Skip to content

Commit 5d3298f

Browse files
committed
fix(deva): support full docker image refs
- normalize embedded tags from env and config for image resolution - split persistent containers by resolved image and preserve installer fallback tags
1 parent 72604f8 commit 5d3298f

3 files changed

Lines changed: 165 additions & 69 deletions

File tree

.github/workflows/ci.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ jobs:
7979
export HOME="$(mktemp -d)"
8080
export PATH="$HOME/.local/bin:$PATH"
8181
export DEVA_INSTALL_BASE_URL="file://$PWD"
82-
export DEVA_DOCKER_IMAGE="deva-smoke:ci"
82+
export DEVA_DOCKER_IMAGE="deva-smoke"
83+
export DEVA_DOCKER_TAG="ci"
8384
export DEVA_DOCKER_IMAGE_FALLBACK=""
8485
export DEVA_NO_DOCKER=1
8586

deva.sh

Lines changed: 104 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,37 @@ LOADED_CONFIGS=()
3939
AGENT_ARGS=()
4040
AGENT_EXPLICIT=false
4141

42+
normalize_docker_image_parts() {
43+
local tail="${DEVA_DOCKER_IMAGE##*/}"
44+
45+
if [[ "$DEVA_DOCKER_IMAGE" == *@* ]]; then
46+
DEVA_DOCKER_TAG=""
47+
DEVA_DOCKER_TAG_ENV_SET=true
48+
return
49+
fi
50+
51+
if [[ "$tail" == *:* ]]; then
52+
local embedded_tag="${tail##*:}"
53+
DEVA_DOCKER_IMAGE="${DEVA_DOCKER_IMAGE%:*}"
54+
if [ "$DEVA_DOCKER_TAG_ENV_SET" = false ]; then
55+
DEVA_DOCKER_TAG="$embedded_tag"
56+
DEVA_DOCKER_TAG_ENV_SET=true
57+
fi
58+
fi
59+
}
60+
61+
docker_image_ref() {
62+
if [[ "$DEVA_DOCKER_IMAGE" == *@* ]]; then
63+
printf '%s' "$DEVA_DOCKER_IMAGE"
64+
elif [ -n "${DEVA_DOCKER_TAG:-}" ]; then
65+
printf '%s:%s' "$DEVA_DOCKER_IMAGE" "$DEVA_DOCKER_TAG"
66+
else
67+
printf '%s' "$DEVA_DOCKER_IMAGE"
68+
fi
69+
}
70+
71+
normalize_docker_image_parts
72+
4273
EPHEMERAL_MODE=false
4374
QUICK_MODE=false
4475
GLOBAL_MODE=false
@@ -90,15 +121,15 @@ Deva flags:
90121
91122
Container Behavior (NEW in v0.8.0):
92123
Default (persistent): Shared per project by default, but split when container shape changes
93-
(extra volumes, explicit config-home, auth mode).
124+
(image/profile, extra volumes, explicit config-home, auth mode).
94125
Preserves state (npm packages, builds, etc).
95126
Faster startup, and default-auth runs can share one warm container.
96127
97128
With --rm (ephemeral): Create new container, auto-remove after exit.
98129
Agent-specific naming for parallel runs.
99130
100131
Container Naming (NEW):
101-
Persistent: deva-<parent>-<project>[..shape] # shape may encode volumes/config/auth
132+
Persistent: deva-<parent>-<project>[..shape] # shape may encode image/volumes/config/auth
102133
Ephemeral: deva-<parent>-<project>-<agent>-<pid> # Agent-specific
103134
104135
Example:
@@ -185,33 +216,37 @@ check_agent() {
185216
}
186217

187218
check_image() {
188-
if docker image inspect "${DEVA_DOCKER_IMAGE}:${DEVA_DOCKER_TAG}" >/dev/null 2>&1; then
219+
local image_ref
220+
image_ref="$(docker_image_ref)"
221+
222+
if docker image inspect "$image_ref" >/dev/null 2>&1; then
189223
return
190224
fi
191225

192226
# Try pulling first
193-
if docker pull "${DEVA_DOCKER_IMAGE}:${DEVA_DOCKER_TAG}" >/dev/null 2>&1; then
227+
if docker pull "$image_ref" >/dev/null 2>&1; then
194228
return
195229
fi
196230

197-
# Smart fallback: check for available profile images locally
231+
# Smart fallback: check for available profile images locally.
232+
# Digest-pinned refs are exact; tag fallback does not make sense there.
198233
local available_tags=""
199-
local original_tag="$DEVA_DOCKER_TAG"
200-
201-
# Check common profile tags (prefer rust as it's a superset of base)
202-
for tag in rust latest; do
203-
if [ "$tag" = "$DEVA_DOCKER_TAG" ]; then
204-
continue # Skip the one we already tried
205-
fi
206-
if docker image inspect "${DEVA_DOCKER_IMAGE}:${tag}" >/dev/null 2>&1; then
207-
available_tags="${available_tags}${tag} "
208-
fi
209-
done
234+
if [[ "$DEVA_DOCKER_IMAGE" != *@* ]]; then
235+
# Check common profile tags (prefer rust as it's a superset of base)
236+
for tag in rust latest; do
237+
if [ "$tag" = "$DEVA_DOCKER_TAG" ]; then
238+
continue # Skip the one we already tried
239+
fi
240+
if docker image inspect "${DEVA_DOCKER_IMAGE}:${tag}" >/dev/null 2>&1; then
241+
available_tags="${available_tags}${tag} "
242+
fi
243+
done
244+
fi
210245

211246
if [ -n "$available_tags" ]; then
212247
# Found alternative images - use the first one
213248
local fallback_tag="${available_tags%% *}" # Get first tag
214-
echo "Image ${DEVA_DOCKER_IMAGE}:${original_tag} not found" >&2
249+
echo "Image $image_ref not found" >&2
215250
echo "Using available image: ${DEVA_DOCKER_IMAGE}:${fallback_tag}" >&2
216251
DEVA_DOCKER_TAG="$fallback_tag"
217252
return
@@ -225,7 +260,7 @@ check_image() {
225260
;;
226261
esac
227262

228-
echo "Docker image ${DEVA_DOCKER_IMAGE}:${DEVA_DOCKER_TAG} not found locally" >&2
263+
echo "Docker image $image_ref not found locally" >&2
229264
if [ -n "$df" ]; then
230265
echo "A matching Dockerfile exists at: $df" >&2
231266
case "${PROFILE:-}" in
@@ -242,7 +277,7 @@ check_image() {
242277
;;
243278
esac
244279
else
245-
echo "Pull with: docker pull ${DEVA_DOCKER_IMAGE}:${DEVA_DOCKER_TAG}" >&2
280+
echo "Pull with: docker pull $image_ref" >&2
246281
fi
247282
exit 1
248283
}
@@ -609,7 +644,7 @@ show_config() {
609644
fi
610645
echo ""
611646
612-
echo "Docker image: ${DEVA_DOCKER_IMAGE}:${DEVA_DOCKER_TAG}"
647+
echo "Docker image: $(docker_image_ref)"
613648
echo "Container prefix: $DEVA_CONTAINER_PREFIX"
614649
}
615650
@@ -634,18 +669,14 @@ prepare_base_docker_args() {
634669
config_hash_source="$CONFIG_ROOT"
635670
fi
636671
fi
637-
if [ -n "$config_hash_source" ]; then
638-
if command -v md5sum >/dev/null 2>&1; then
639-
config_hash=$(printf '%s' "$config_hash_source" | md5sum | cut -c1-6)
640-
elif command -v shasum >/dev/null 2>&1; then
641-
config_hash=$(printf '%s' "$config_hash_source" | shasum | cut -c1-6)
642-
else
643-
config_hash=$(printf '%s' "$config_hash_source" | cksum | cut -d' ' -f1 | cut -c1-6)
644-
fi
645-
fi
672+
[ -n "$config_hash_source" ] && config_hash=$(short_hash "$config_hash_source" 6)
673+
674+
local image_hash=""
675+
image_hash=$(short_hash "$(docker_image_ref)" 6)
646676
647677
local suffix=""
648-
[ -n "$volume_hash" ] && suffix="..v${volume_hash}"
678+
[ -n "$image_hash" ] && suffix="..i${image_hash}"
679+
[ -n "$volume_hash" ] && suffix="${suffix}..v${volume_hash}"
649680
[ -n "$config_hash" ] && suffix="${suffix}..c${config_hash}"
650681
651682
if [ "$EPHEMERAL_MODE" = true ]; then
@@ -677,10 +708,14 @@ prepare_base_docker_args() {
677708
--label "deva.workspace_hash=${ws_hash}"
678709
--label "deva.agent=${ACTIVE_AGENT}"
679710
--label "deva.ephemeral=${EPHEMERAL_MODE}"
711+
--label "deva.image=$(docker_image_ref)"
680712
)
681713
if [ -n "$volume_hash" ]; then
682714
DOCKER_ARGS+=(--label "deva.volhash=${volume_hash}")
683715
fi
716+
if [ -n "$image_hash" ]; then
717+
DOCKER_ARGS+=(--label "deva.image_hash=${image_hash}")
718+
fi
684719
685720
if [ -n "${LANG:-}" ]; then DOCKER_ARGS+=(-e "LANG=$LANG"); fi
686721
if [ -n "${LC_ALL:-}" ]; then DOCKER_ARGS+=(-e "LC_ALL=$LC_ALL"); fi
@@ -1139,6 +1174,19 @@ normalize_volume_spec() {
11391174
echo "$src:$remainder"
11401175
}
11411176
1177+
short_hash() {
1178+
local input="$1"
1179+
local length="${2:-8}"
1180+
1181+
if command -v md5sum >/dev/null 2>&1; then
1182+
printf '%s' "$input" | md5sum | cut -c1-"$length"
1183+
elif command -v shasum >/dev/null 2>&1; then
1184+
printf '%s' "$input" | shasum | cut -c1-"$length"
1185+
else
1186+
printf '%s' "$input" | cksum | cut -d' ' -f1 | cut -c1-"$length"
1187+
fi
1188+
}
1189+
11421190
compute_volume_hash() {
11431191
if [ ${#USER_VOLUMES[@]} -eq 0 ]; then
11441192
return
@@ -1161,15 +1209,7 @@ compute_volume_hash() {
11611209
hash_input="${hash_input}${src}:${vol#*:}|"
11621210
done <<<"$sorted_vols"
11631211
1164-
if [ -n "$hash_input" ]; then
1165-
if command -v md5sum >/dev/null 2>&1; then
1166-
echo "$hash_input" | md5sum | cut -c1-8
1167-
elif command -v shasum >/dev/null 2>&1; then
1168-
echo "$hash_input" | shasum | cut -c1-8
1169-
else
1170-
echo "$hash_input" | cksum | cut -d' ' -f1 | cut -c1-8
1171-
fi
1172-
fi
1212+
[ -n "$hash_input" ] && short_hash "$hash_input" 8
11731213
}
11741214
11751215
workspace_hash() {
@@ -1180,13 +1220,7 @@ workspace_hash() {
11801220
11811221
local p
11821222
p="$(pwd)"
1183-
if command -v md5sum >/dev/null 2>&1; then
1184-
_WS_HASH_CACHE=$(printf '%s' "$p" | md5sum | cut -c1-8)
1185-
elif command -v shasum >/dev/null 2>&1; then
1186-
_WS_HASH_CACHE=$(printf '%s' "$p" | shasum | cut -c1-8)
1187-
else
1188-
_WS_HASH_CACHE=$(printf '%s' "$p" | cksum | cut -d' ' -f1 | cut -c1-8)
1189-
fi
1223+
_WS_HASH_CACHE=$(short_hash "$p" 8)
11901224
printf '%s' "$_WS_HASH_CACHE"
11911225
}
11921226
@@ -1399,6 +1433,20 @@ process_var_config() {
13991433
set_config_home_value "$value"
14001434
fi
14011435
;;
1436+
DEVA_DOCKER_IMAGE)
1437+
DEVA_DOCKER_IMAGE="$value"
1438+
DEVA_DOCKER_IMAGE_ENV_SET=true
1439+
normalize_docker_image_parts
1440+
export DEVA_DOCKER_IMAGE
1441+
USER_ENVS+=("$name=$value")
1442+
;;
1443+
DEVA_DOCKER_TAG)
1444+
DEVA_DOCKER_TAG="$value"
1445+
DEVA_DOCKER_TAG_ENV_SET=true
1446+
normalize_docker_image_parts
1447+
export DEVA_DOCKER_TAG
1448+
USER_ENVS+=("$name=$value")
1449+
;;
14021450
DEFAULT_AGENT)
14031451
DEFAULT_AGENT="$value"
14041452
;;
@@ -1716,7 +1764,7 @@ if [ ${#PRE_ARGS[@]} -gt 0 ]; then
17161764
;;
17171765
--version)
17181766
echo "deva.sh v${VERSION}"
1719-
echo "Docker Image: ${DEVA_DOCKER_IMAGE}:${DEVA_DOCKER_TAG}"
1767+
echo "Docker Image: $(docker_image_ref)"
17201768
exit 0
17211769
;;
17221770
--show-config)
@@ -1766,6 +1814,7 @@ if [ "$MANAGEMENT_MODE" = "shell" ] || [ "$MANAGEMENT_MODE" = "ps" ] || [ "$MANA
17661814
if [ -z "$ACTIVE_AGENT" ]; then
17671815
ACTIVE_AGENT="$DEFAULT_AGENT"
17681816
fi
1817+
resolve_profile
17691818
show_config
17701819
exit 0
17711820
fi
@@ -2232,26 +2281,14 @@ if [ -n "${AUTH_METHOD:-}" ]; then
22322281
auth_config_src="$CONFIG_ROOT"
22332282
fi
22342283
fi
2235-
if [ -n "$auth_config_src" ]; then
2236-
if command -v md5sum >/dev/null 2>&1; then
2237-
auth_config_hash=$(printf '%s' "$auth_config_src" | md5sum | cut -c1-6)
2238-
elif command -v shasum >/dev/null 2>&1; then
2239-
auth_config_hash=$(printf '%s' "$auth_config_src" | shasum | cut -c1-6)
2240-
else
2241-
auth_config_hash=$(printf '%s' "$auth_config_src" | cksum | cut -d' ' -f1 | cut -c1-6)
2242-
fi
2243-
fi
2284+
[ -n "$auth_config_src" ] && auth_config_hash=$(short_hash "$auth_config_src" 6)
2285+
2286+
image_hash=$(short_hash "$(docker_image_ref)" 6)
22442287
22452288
# Hash credential file path for credentials-file auth
22462289
creds_hash=""
22472290
if [ "$AUTH_METHOD" = "credentials-file" ] && [ -n "${CUSTOM_CREDENTIALS_FILE:-}" ]; then
2248-
if command -v md5sum >/dev/null 2>&1; then
2249-
creds_hash=$(printf '%s' "$CUSTOM_CREDENTIALS_FILE" | md5sum | cut -c1-8)
2250-
elif command -v shasum >/dev/null 2>&1; then
2251-
creds_hash=$(printf '%s' "$CUSTOM_CREDENTIALS_FILE" | shasum | cut -c1-8)
2252-
else
2253-
creds_hash=$(printf '%s' "$CUSTOM_CREDENTIALS_FILE" | cksum | cut -d' ' -f1 | cut -c1-8)
2254-
fi
2291+
creds_hash=$(short_hash "$CUSTOM_CREDENTIALS_FILE" 8)
22552292
fi
22562293
22572294
new_container_name=""
@@ -2265,7 +2302,8 @@ if [ -n "${AUTH_METHOD:-}" ]; then
22652302
22662303
# Build suffix chain: volume + config + auth
22672304
name_suffix=""
2268-
[ -n "$volume_hash" ] && name_suffix="..v${volume_hash}"
2305+
[ -n "$image_hash" ] && name_suffix="..i${image_hash}"
2306+
[ -n "$volume_hash" ] && name_suffix="${name_suffix}..v${volume_hash}"
22692307
[ -n "$auth_config_hash" ] && name_suffix="${name_suffix}..c${auth_config_hash}"
22702308
name_suffix="${name_suffix}..${auth_suffix}"
22712309
@@ -2360,7 +2398,7 @@ if [ "$QUICK_MODE" = false ] && [ -d "$(pwd)/.claude" ]; then
23602398
DOCKER_ARGS+=("-v" "$(pwd)/.claude:$(pwd)/.claude")
23612399
fi
23622400
2363-
DOCKER_ARGS+=("${DEVA_DOCKER_IMAGE}:${DEVA_DOCKER_TAG}")
2401+
DOCKER_ARGS+=("$(docker_image_ref)")
23642402
23652403
if [ "$ACTION" = "shell" ]; then
23662404
AGENT_COMMAND=("/bin/zsh")
@@ -2479,7 +2517,7 @@ if [ "$EPHEMERAL_MODE" = false ]; then
24792517
24802518
exec docker exec "${DOCKER_TERMINAL_ARGS[@]}" "$CONTAINER_NAME" /usr/local/bin/docker-entrypoint.sh "${AGENT_COMMAND[@]}"
24812519
else
2482-
echo "Launching ${ACTIVE_AGENT} (ephemeral mode) via ${DEVA_DOCKER_IMAGE}:${DEVA_DOCKER_TAG}"
2520+
echo "Launching ${ACTIVE_AGENT} (ephemeral mode) via $(docker_image_ref)"
24832521
write_session_file
24842522
if ! docker "${DOCKER_ARGS[@]}" "${AGENT_COMMAND[@]}"; then
24852523
echo "error: failed to launch ephemeral container" >&2

0 commit comments

Comments
 (0)