summaryrefslogtreecommitdiffstats
path: root/recipes-containers/vcontainer/files/vdkr-init.sh
blob: 084a8791b6bff19937d266b2ba64ec4393d7503b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
#!/bin/sh
# SPDX-FileCopyrightText: Copyright (C) 2025 Bruce Ashfield
#
# SPDX-License-Identifier: GPL-2.0-only
#
# vdkr-init.sh
# Init script for vdkr: execute arbitrary docker commands in QEMU
#
# This script runs on a real ext4 filesystem after switch_root from initramfs.
# The preinit script mounted /dev/vda (rootfs.img) and did switch_root to us.
#
# Drive layout (rootfs.img is always /dev/vda, mounted as /):
#   /dev/vda = rootfs.img (this script runs from here, mounted as /)
#   /dev/vdb = input disk (optional, OCI/tar/dir data)
#   /dev/vdc = state disk (optional, persistent Docker storage)
#
# Kernel parameters:
#   docker_cmd=<base64>    Base64-encoded docker command + args
#   docker_input=<type>    Input type: none, oci, tar, dir (default: none)
#   docker_output=<type>   Output type: text, tar, storage (default: text)
#   docker_state=<type>    State type: none, disk (default: none)
#   docker_network=1       Enable networking (configure eth0, DNS)
#   docker_registry=<url>  Default registry for unqualified images (e.g., 10.0.2.2:5000/yocto)
#   docker_insecure_registry=<host:port>  Mark registry as insecure (HTTP). Can repeat.
#
# Version: 2.4.0

# Set runtime-specific parameters before sourcing common code
VCONTAINER_RUNTIME_NAME="vdkr"
VCONTAINER_RUNTIME_CMD="docker"
VCONTAINER_RUNTIME_PREFIX="docker"
VCONTAINER_STATE_DIR="/var/lib/docker"
VCONTAINER_SHARE_NAME="vdkr_share"
VCONTAINER_VERSION="2.4.0"

# Docker-specific: default registry for unqualified image names
# Set via kernel param: docker_registry=10.0.2.2:5000/yocto
# Or baked into rootfs: /etc/vdkr/registry.conf
DOCKER_DEFAULT_REGISTRY=""

# Source common init functions
# When installed as /init, common file is at /vcontainer-init-common.sh
. /vcontainer-init-common.sh

# Load baked-in registry defaults from /etc/vdkr/registry.conf
# These can be overridden by kernel cmdline parameters
load_registry_config() {
    if [ -f /etc/vdkr/registry.conf ]; then
        . /etc/vdkr/registry.conf
        # Map config file variables to our internal variables
        if [ -n "$VDKR_DEFAULT_REGISTRY" ]; then
            DOCKER_DEFAULT_REGISTRY="$VDKR_DEFAULT_REGISTRY"
            log "Loaded baked registry: $DOCKER_DEFAULT_REGISTRY"
        fi
        # VDKR_INSECURE_REGISTRIES is handled in start_dockerd
    fi
}

# ============================================================================
# Docker-Specific Functions
# ============================================================================

setup_docker_storage() {
    mkdir -p /run/containerd /run/lock
    mkdir -p /var/lib/docker
    mkdir -p /var/lib/containerd

    # Handle Docker storage
    if [ -n "$STATE_DISK" ] && [ -b "$STATE_DISK" ]; then
        log "Mounting state disk $STATE_DISK as /var/lib/docker..."
        if mount -t ext4 "$STATE_DISK" /var/lib/docker 2>&1; then
            log "SUCCESS: Mounted $STATE_DISK as Docker storage"
            log "Docker storage contents:"
            [ "$QUIET_BOOT" = "0" ] && ls -la /var/lib/docker/ 2>/dev/null || log "(empty)"
        else
            log "WARNING: Failed to mount state disk, using tmpfs"
            RUNTIME_STATE="none"
        fi
    fi

    # If no state disk, use tmpfs for Docker storage
    if [ "$RUNTIME_STATE" != "disk" ]; then
        log "Using tmpfs for Docker storage (ephemeral)..."
        mount -t tmpfs -o size=1G tmpfs /var/lib/docker
    fi
}

start_containerd() {
    CONTAINERD_READY=false
    if [ -x "/usr/bin/containerd" ]; then
        log "Starting containerd..."
        mkdir -p /var/lib/containerd
        mkdir -p /run/containerd
        /usr/bin/containerd --log-level info --root /var/lib/containerd --state /run/containerd >/tmp/containerd.log 2>&1 &
        CONTAINERD_PID=$!
        # Wait for containerd socket
        for i in 1 2 3 4 5 6 7 8 9 10; do
            if [ -S /run/containerd/containerd.sock ]; then
                log "Containerd running (PID: $CONTAINERD_PID)"
                CONTAINERD_READY=true
                break
            fi
            sleep 1
        done
        if [ "$CONTAINERD_READY" != "true" ]; then
            log "WARNING: Containerd failed to start, check /tmp/containerd.log"
            [ -f /tmp/containerd.log ] && cat /tmp/containerd.log >&2
        fi
    fi
}

start_dockerd() {
    log "Starting Docker daemon..."
    DOCKER_OPTS="--data-root=/var/lib/docker"
    DOCKER_OPTS="$DOCKER_OPTS --storage-driver=overlay2"
    # Enable iptables for Docker bridge NAT and port forwarding
    DOCKER_OPTS="$DOCKER_OPTS --iptables=true"
    DOCKER_OPTS="$DOCKER_OPTS --userland-proxy=false"
    # Use default docker0 bridge (172.17.0.0/16) for container networking
    DOCKER_OPTS="$DOCKER_OPTS --host=unix:///var/run/docker.sock"
    DOCKER_OPTS="$DOCKER_OPTS --exec-opt native.cgroupdriver=cgroupfs"
    DOCKER_OPTS="$DOCKER_OPTS --log-level=info"

    # Parse default registry from kernel cmdline (docker_registry=host:port/namespace)
    # Kernel cmdline OVERRIDES baked config from /etc/vdkr/registry.conf
    # Use docker_registry=none to explicitly disable baked registry
    # This enables: "docker pull container-base" → "docker pull 10.0.2.2:5000/yocto/container-base"
    GREP_RESULT=$(grep -o 'docker_registry=[^ ]*' /proc/cmdline 2>/dev/null || true)
    if [ -n "$GREP_RESULT" ]; then
        CMDLINE_REGISTRY=$(echo "$GREP_RESULT" | sed 's/docker_registry=//')
        if [ "$CMDLINE_REGISTRY" = "none" ] || [ -z "$CMDLINE_REGISTRY" ]; then
            DOCKER_DEFAULT_REGISTRY=""
            log "Registry disabled via cmdline"
        else
            DOCKER_DEFAULT_REGISTRY="$CMDLINE_REGISTRY"
            log "Registry from cmdline: $DOCKER_DEFAULT_REGISTRY"
        fi
    elif [ -n "$DOCKER_DEFAULT_REGISTRY" ]; then
        log "Registry from baked config: $DOCKER_DEFAULT_REGISTRY"
    fi
    if [ -n "$DOCKER_DEFAULT_REGISTRY" ]; then
        # Extract host:port for insecure registry config (strip path/namespace)
        REGISTRY_HOST=$(echo "$DOCKER_DEFAULT_REGISTRY" | cut -d'/' -f1)
        # Auto-add to insecure registries if it looks like a local/private registry
        if echo "$REGISTRY_HOST" | grep -qE '^(localhost|127\.|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)'; then
            DOCKER_OPTS="$DOCKER_OPTS --insecure-registry=$REGISTRY_HOST"
            log "Auto-added insecure registry: $REGISTRY_HOST"
        fi
    fi

    # Add baked insecure registries from /etc/vdkr/registry.conf
    if [ -n "$VDKR_INSECURE_REGISTRIES" ]; then
        for registry in $VDKR_INSECURE_REGISTRIES; do
            DOCKER_OPTS="$DOCKER_OPTS --insecure-registry=$registry"
            log "Added baked insecure registry: $registry"
        done
    fi

    # Check for additional insecure registries from kernel cmdline (docker_insecure_registry=host:port)
    # For local registry on build host via QEMU slirp: docker_insecure_registry=10.0.2.2:5000
    # For remote HTTP registry: docker_insecure_registry=registry.company.com:5000
    # Multiple registries can be specified by repeating the parameter
    for registry in $(grep -o 'docker_insecure_registry=[^ ]*' /proc/cmdline 2>/dev/null | sed 's/docker_insecure_registry=//' || true); do
        if [ -n "$registry" ]; then
            DOCKER_OPTS="$DOCKER_OPTS --insecure-registry=$registry"
            log "Added insecure registry: $registry"
        fi
    done

    if [ "$CONTAINERD_READY" = "true" ]; then
        DOCKER_OPTS="$DOCKER_OPTS --containerd=/run/containerd/containerd.sock"
    fi

    /usr/bin/dockerd $DOCKER_OPTS >/var/log/docker.log 2>&1 &
    DOCKER_PID=$!
    log "Docker daemon started (PID: $DOCKER_PID)"

    # Wait for Docker to be ready
    log "Waiting for Docker daemon..."
    DOCKER_READY=false

    sleep 5

    for i in $(seq 1 60); do
        if ! kill -0 $DOCKER_PID 2>/dev/null; then
            echo "===ERROR==="
            echo "Docker daemon died after $i iterations"
            echo "Docker log:"
            cat /var/log/docker.log 2>/dev/null || true
            dmesg | tail -20 2>/dev/null || true
            sleep 2
            reboot -f
        fi

        # Try docker info and capture any error
        DOCKER_INFO_OUT=$(/usr/bin/docker info 2>&1)
        DOCKER_INFO_RC=$?
        if [ $DOCKER_INFO_RC -eq 0 ]; then
            log "Docker daemon is ready!"
            DOCKER_READY=true
            break
        fi

        log "Waiting... ($i/60) - docker info rc=$DOCKER_INFO_RC"
        # Show first line of error on every 10th iteration
        if [ $((i % 10)) -eq 0 ]; then
            echo "docker info error: $(echo "$DOCKER_INFO_OUT" | head -1)"
        fi
        sleep 2
    done

    if [ "$DOCKER_READY" != "true" ]; then
        echo "===ERROR==="
        echo "Docker failed to start after 60 attempts"
        echo "Last docker info output:"
        echo "$DOCKER_INFO_OUT" | head -5
        echo "Docker log tail:"
        tail -20 /var/log/docker.log 2>/dev/null || true
        sleep 5
        reboot -f
    fi
}

stop_runtime_daemons() {
    # Stop Docker daemon
    if [ -n "$DOCKER_PID" ]; then
        log "Stopping Docker daemon..."
        kill $DOCKER_PID 2>/dev/null || true
        for i in $(seq 1 10); do
            if ! kill -0 $DOCKER_PID 2>/dev/null; then
                log "Docker daemon stopped"
                break
            fi
            sleep 1
        done
    fi

    # Stop containerd
    if [ -n "$CONTAINERD_PID" ]; then
        log "Stopping containerd..."
        kill $CONTAINERD_PID 2>/dev/null || true
        sleep 2
    fi
}

# Execute a pull command with registry fallback
# Tries registry first, falls back to Docker Hub if image not found
# Usage: execute_pull_with_fallback "docker pull alpine:latest"
# Returns: exit code of successful pull, or last failure
execute_pull_with_fallback() {
    local cmd="$1"
    local image=""
    local tag=""

    # Extract image name from pull command
    # Handles: docker pull <image> or docker pull <image>:tag
    if echo "$cmd" | grep -qE '^docker pull '; then
        image=$(echo "$cmd" | awk '{print $3}')
    else
        # Not a pull command, just execute it
        eval "$cmd"
        return $?
    fi

    # If no registry configured, just run the original command
    if [ -z "$DOCKER_DEFAULT_REGISTRY" ]; then
        log "No registry configured, pulling from Docker Hub"
        eval "$cmd"
        return $?
    fi

    # Check if image is already qualified (has / in it)
    if echo "$image" | grep -q '/'; then
        # Already qualified (e.g., docker.io/library/alpine or myregistry/image)
        log "Image already qualified: $image"
        eval "$cmd"
        return $?
    fi

    # Unqualified image - try registry first, then Docker Hub
    local registry_image="$DOCKER_DEFAULT_REGISTRY/$image"

    log "Trying registry first: $registry_image"
    if docker pull "$registry_image" 2>/dev/null; then
        log "Successfully pulled from registry: $registry_image"
        docker images | grep -E "REPOSITORY|$image" || true
        return 0
    fi

    log "Image not in registry, falling back to Docker Hub: $image"
    if docker pull "$image"; then
        log "Successfully pulled from Docker Hub: $image"
        docker images | grep -E "REPOSITORY|$image" || true
        return 0
    fi

    log "ERROR: Failed to pull $image from both registry and Docker Hub"
    return 1
}

# Check if a command is a pull command that needs fallback handling
is_pull_command() {
    local cmd="$1"
    echo "$cmd" | grep -qE '^docker pull '
}

# Helper function to check if an image exists locally
# Returns 0 if exists, 1 if not
image_exists_locally() {
    local img="$1"
    # Try exact match first, then with :latest suffix
    if docker images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -qE "^${img}$"; then
        return 0
    fi
    # If no tag specified, try with :latest
    if ! echo "$img" | grep -q ':'; then
        if docker images --format '{{.Repository}}:{{.Tag}}' 2>/dev/null | grep -qE "^${img}:latest$"; then
            return 0
        fi
    fi
    return 1
}

# Helper function to transform an unqualified image name
# Must be defined before transform_docker_command which uses it
# Priority: 1) local image as-is, 2) with registry prefix, 3) unchanged
transform_image_name() {
    local img="$1"
    if [ -z "$img" ]; then
        echo ""
        return
    fi
    # Check if this is an image ID (hex string) - don't transform
    # Short form: 12 hex chars (e7b39c54cdec)
    # Long form: sha256:64 hex chars
    if echo "$img" | grep -qE '^[0-9a-fA-F]{12,64}$'; then
        echo "$img"
        return
    fi
    if echo "$img" | grep -qE '^sha256:[0-9a-fA-F]{64}$'; then
        echo "$img"
        return
    fi
    # Check if image is unqualified (no /)
    if ! echo "$img" | grep -q '/'; then
        # First check if image exists locally as-is
        if image_exists_locally "$img"; then
            echo "$img"
            return
        fi
        # If not local and we have a default registry, use it
        if [ -n "$DOCKER_DEFAULT_REGISTRY" ]; then
            echo "$DOCKER_DEFAULT_REGISTRY/$img"
            return
        fi
        # No registry configured, use as-is (Docker will try Docker Hub)
        echo "$img"
    # Check if already has registry with port - don't transform
    elif echo "$img" | grep -qE '^[^/]+:[0-9]+/'; then
        echo "$img"
    # Check if looks like a domain - don't transform
    elif echo "$img" | grep -qE '^[a-zA-Z0-9-]+\.[a-zA-Z]'; then
        echo "$img"
    else
        echo "$img"
    fi
}

# Transform docker commands to use default registry for unqualified images
# "docker pull container-base" → "docker pull 10.0.2.2:5000/yocto/container-base"
# "docker pull alpine" → "docker pull 10.0.2.2:5000/yocto/alpine" (if registry set)
# "docker pull docker.io/library/alpine" → unchanged (already qualified)
# Also handles "docker image *" compound commands and other image commands
#
# NOTE: Pull commands are NOT transformed here - they use execute_pull_with_fallback
# which tries registry first, then Docker Hub as fallback.
transform_docker_command() {
    local cmd="$1"

    # Handle "docker image *" compound commands - convert to standard form
    # docker image pull → docker pull
    # docker image rm → docker rmi
    # docker image ls → docker images
    # docker image inspect → docker inspect (works for images)
    if echo "$cmd" | grep -qE '^docker image '; then
        local subcmd=$(echo "$cmd" | awk '{print $3}')
        local rest=$(echo "$cmd" | cut -d' ' -f4-)
        case "$subcmd" in
            pull)    cmd="docker pull $rest" ;;
            rm)      cmd="docker rmi $rest" ;;
            ls)      cmd="docker images $rest" ;;
            inspect) cmd="docker inspect $rest" ;;
            tag)     cmd="docker tag $rest" ;;
            push)    cmd="docker push $rest" ;;
            prune)   cmd="docker image prune $rest" ;;  # keep as-is, docker supports it
            history) cmd="docker history $rest" ;;
            *)       ;;  # pass through unknown subcommands
        esac
    fi

    # Only transform if default registry is configured
    if [ -z "$DOCKER_DEFAULT_REGISTRY" ]; then
        echo "$cmd"
        return
    fi

    # NOTE: docker images, inspect, history, rmi, tag do NOT get transformed.
    # These commands operate on local images - the user specifies exactly what they have.
    # Transform only applies to pull/run where we're fetching images.
    #
    # If user has:
    #   - alpine:latest (from Docker Hub via fallback)
    #   - 10.0.2.2:5000/yocto/myapp:latest (from registry)
    #
    # Then:
    #   - "docker images alpine" → shows alpine:latest (no transform)
    #   - "docker inspect alpine" → inspects alpine:latest (no transform)
    #   - "docker rmi alpine" → removes alpine:latest (no transform)

    # Pull commands are handled by execute_pull_with_fallback, not transformed here
    if echo "$cmd" | grep -qE '^docker pull '; then
        echo "$cmd"
        return
    fi

    # Check if this is a run command
    if echo "$cmd" | grep -qE '^docker run '; then
        # Extract the image reference (handles "docker run [opts] img [cmd]")
        local docker_cmd="run"
        local rest=""

        if [ "$docker_cmd" = "run" ]; then
            # docker run [options] <image> [command]
            # This is trickier - image is the first non-option argument
            # For simplicity, look for image pattern after run
            # Skip known options that take arguments
            local args=$(echo "$cmd" | cut -d' ' -f3-)
            local image=""
            local new_args=""
            local skip_next=false

            for arg in $args; do
                # Once we have the image, everything else is the container command
                if [ -n "$image" ]; then
                    rest="$rest $arg"
                    continue
                fi

                if [ "$skip_next" = "true" ]; then
                    new_args="$new_args $arg"
                    skip_next=false
                    continue
                fi

                case "$arg" in
                    -d|--detach|-i|--interactive|-t|--tty|--rm|--privileged)
                        new_args="$new_args $arg"
                        ;;
                    -p|--publish|-v|--volume|-e|--env|--name|--network|-w|--workdir|--entrypoint|-m|--memory|--cpus|--cpu-shares)
                        new_args="$new_args $arg"
                        skip_next=true
                        ;;
                    -p=*|--publish=*|-v=*|--volume=*|-e=*|--env=*|--name=*|--network=*|-w=*|--workdir=*|--entrypoint=*|-m=*|--memory=*)
                        new_args="$new_args $arg"
                        ;;
                    -*)
                        # Other options, pass through
                        new_args="$new_args $arg"
                        ;;
                    *)
                        # First non-option is the image
                        image="$arg"
                        ;;
                esac
            done

            if [ -n "$image" ]; then
                local transformed=$(transform_image_name "$image")
                echo "docker run$new_args $transformed$rest"
                return
            fi
        fi
    fi

    # Return unchanged
    echo "$cmd"
}

handle_storage_output() {
    echo "Stopping Docker gracefully..."
    /usr/bin/docker system prune -f >/dev/null 2>&1 || true
    kill $DOCKER_PID 2>/dev/null || true
    [ -n "$CONTAINERD_PID" ] && kill $CONTAINERD_PID 2>/dev/null || true
    sleep 3

    echo "Packaging Docker storage..."
    cd /var/lib
    tar -cf /tmp/storage.tar docker/

    STORAGE_SIZE=$(stat -c%s /tmp/storage.tar 2>/dev/null || echo "0")
    echo "Storage size: $STORAGE_SIZE bytes"

    if [ "$STORAGE_SIZE" -gt 1000 ]; then
        # Use virtio-9p if available (much faster than console base64)
        if [ "$RUNTIME_9P" = "1" ] && mountpoint -q /mnt/share 2>/dev/null; then
            echo "Using virtio-9p for storage output (fast path)"
            cp /tmp/storage.tar /mnt/share/storage.tar
            sync
            echo "===9P_STORAGE_DONE==="
            echo "===EXIT_CODE=$EXEC_EXIT_CODE==="
        else
            # Fallback: base64 to console (slow)
            dmesg -n 1
            echo "===STORAGE_START==="
            base64 /tmp/storage.tar
            echo "===STORAGE_END==="
            echo "===EXIT_CODE=$EXEC_EXIT_CODE==="
        fi
    else
        echo "===ERROR==="
        echo "Storage too small"
    fi
}

# ============================================================================
# Main
# ============================================================================

# Initialize base environment
setup_base_environment
mount_base_filesystems

# Check for quiet boot mode
check_quiet_boot

log "=== vdkr Init ==="
log "Version: $VCONTAINER_VERSION"

# Mount tmpfs directories and cgroups
mount_tmpfs_dirs
setup_cgroups

# Parse kernel command line
parse_cmdline

# Mount virtio-9p share if available (for fast storage output in batch-import mode)
if [ "$RUNTIME_9P" = "1" ]; then
    mkdir -p /mnt/share
    if mount -t 9p -o trans=virtio,version=9p2000.L,cache=none ${VCONTAINER_SHARE_NAME} /mnt/share 2>/dev/null; then
        log "Mounted virtio-9p share at /mnt/share (fast I/O enabled)"
    else
        log "WARNING: Could not mount virtio-9p share, falling back to console output"
        RUNTIME_9P="0"
    fi
fi

# Detect and configure disks
detect_disks

# Set up Docker storage (Docker-specific)
setup_docker_storage

# Mount input disk
mount_input_disk

# Configure networking
configure_networking

# Load baked registry config (can be overridden by kernel cmdline)
load_registry_config

# Start containerd and dockerd (Docker-specific)
start_containerd
start_dockerd

# Handle daemon mode or single command execution
if [ "$RUNTIME_DAEMON" = "1" ]; then
    # Export registry for daemon mode
    # Note: Functions (execute_pull_with_fallback, is_pull_command) are already
    # available since they're defined in this script before run_daemon_mode is called
    export DOCKER_DEFAULT_REGISTRY
    run_daemon_mode
else
    prepare_input_path
    # Check if this is a pull command - use fallback logic
    if is_pull_command "$RUNTIME_CMD"; then
        # Pull commands use registry-first, Docker Hub fallback
        log "Using pull with registry fallback"
        execute_pull_with_fallback "$RUNTIME_CMD"
        EXEC_EXIT_CODE=$?
        echo "===EXIT_CODE=$EXEC_EXIT_CODE==="
        graceful_shutdown
        exit 0
    fi
    # Transform other commands to use default registry for unqualified images
    if [ -n "$DOCKER_DEFAULT_REGISTRY" ]; then
        RUNTIME_CMD=$(transform_docker_command "$RUNTIME_CMD")
    fi
    execute_command
fi

# Graceful shutdown
graceful_shutdown