#!/usr/bin/env bash # ============================================================================== # OFELIA-SCRIPT-BOOTSTRAP: Exec-Mode Orchestrator # Runs INSIDE the Ofelia master container (via an exec API job). # ============================================================================== set -euo pipefail log() { local level="$1" local message="$2" printf "%s [%s] %s\n" "$(date '+%Y-%m-%d %H:%M:%S')" "$level" "$message" } log "INFO" "Initializing Exec-Mode Bootstrap Sequence" export EXEC_ID=$(uuidgen | cut -d- -f1) # 1. SELF-DISCOVERY # We ARE the master daemon. hostname == master container name (set by --name). SELF_ID=$(hostname) log "DEBUG" "Self-ID (master): $SELF_ID" MASTER_JSON=$(docker inspect "$SELF_ID" 2>/dev/null || true) if [ -z "$MASTER_JSON" ] || [ "$MASTER_JSON" == "[]" ]; then log "ERROR" "Cannot inspect self [$SELF_ID]. Docker socket unavailable?" exit 1 fi # 2. SOURCE INFERENCE HOST_SRC_PATH=$(echo "$MASTER_JSON" | jq -r '.[0].Mounts[] | select(.Destination == "/src") | .Source' | head -n 1) if [ -z "$HOST_SRC_PATH" ]; then log "ERROR" "No /src mount on master. Cannot infer build context." exit 1 fi log "INFO" "Source context: $HOST_SRC_PATH" # 3. BUILD PAYLOAD IMAGE # The build context is mounted at /src (read-only) on the master. # We tar it and pipe into docker build, exactly as before. log "INFO" "Compiling payload image ($EXEC_ID) with cache bust..." docker run --rm --privileged -v "${HOST_SRC_PATH}:/ctx:ro" alpine:latest tar -C /ctx -cf - . \ | DOCKER_BUILDKIT=0 docker build \ --build-arg CACHE_BUST=$(date +%s) \ -t "$EXEC_ID:latest" \ -f Dockerfile.tooling - # 4. MIRROR MASTER CONTEXT ONTO WORKER declare -a RUN_ARGS=() # Network: mirror every non-default network the master is attached to. while IFS= read -r net; do if [ -n "$net" ] && [[ ! "$net" =~ ^(bridge|host|none|null|default)$ ]]; then RUN_ARGS+=("--net" "$net") log "DEBUG" "Mirroring network: $net" fi done < <(echo "$MASTER_JSON" | jq -r '.[0].NetworkSettings.Networks | keys[]' 2>/dev/null || true) # Mounts: mirror everything except the Docker socket (added explicitly) # and the Ofelia config file (not needed by the worker). while IFS= read -r mnt; do [ -z "$mnt" ] && continue RUN_ARGS+=("--mount" "$mnt") log "DEBUG" "Mirroring mount: $mnt" done < <(echo "$MASTER_JSON" | jq -r ' .[0].Mounts[] | select(.Source != "/var/run/docker.sock" and (.Destination | startswith("/etc/config.ini") | not)) | "type=" + .Type + ",source=" + .Source + ",target=" + .Destination + (if .Propagation then ",bind-propagation=" + .Propagation else "" end) + (if .RW == false then ",readonly" else "" end)') # Identity & resources: mirror the master's user, memory, cpus. MEM=$(echo "$MASTER_JSON" | jq -r '.[0].HostConfig.Memory // 0') CPU=$(echo "$MASTER_JSON" | jq -r '.[0].HostConfig.NanoCpus // 0') USER_VAL=$(echo "$MASTER_JSON" | jq -r '.[0].Config.User // "0:0"') [ "$MEM" -gt 0 ] && RUN_ARGS+=("--memory" "$MEM") [ "$CPU" -gt 0 ] && RUN_ARGS+=("--cpus" "$(echo "scale=2; $CPU / 1000000000" | bc)") RUN_ARGS+=("--user" "${USER_VAL:-0:0}") # 5. LAUNCH WORKER WORKER_NAME="${SELF_ID}-worker" log "INFO" "Launching payload worker: $WORKER_NAME" # Remove any stale worker from a previous crashed run. docker rm -f "$WORKER_NAME" >/dev/null 2>&1 || true exec docker run \ --init \ --name "$WORKER_NAME" \ --hostname "$WORKER_NAME" \ --rm \ -t \ "${RUN_ARGS[@]}" \ -v "/var/run/docker.sock:/var/run/docker.sock" \ "$EXEC_ID:latest" "/src/script" 2>&1