#!/usr/bin/env bash # # dc.sh — devcontainer helper for this repo # # Usage: # ./devcontainer.sh # # Configs: backend | asset_list # Commands: up, shell, down, rebuild # # `shell` auto-runs `up` first if the container isn't already running, # so it's safe to call cold. # # Examples: # ./devcontainer.sh backend shell # up + exec bash # ./devcontainer.sh asset_list up # ./devcontainer.sh backend rebuild # ./devcontainer.sh backend down set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" REPO_ROOT="${SCRIPT_DIR}" VALID_CONFIGS=(backend asset_list) VALID_COMMANDS=(up shell down rebuild) # --- helpers --------------------------------------------------------------- usage() { sed -n '3,20p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' exit "${1:-0}" } die() { echo "error: $*" >&2 exit 1 } in_list() { # in_list local needle="$1" shift local item for item in "$@"; do [[ "${item}" == "${needle}" ]] && return 0 done return 1 } container_id_for() { # Find a running container for the given config path via devcontainer labels. local config_path="$1" docker ps -q \ --filter "label=devcontainer.local_folder=${REPO_ROOT}" \ --filter "label=devcontainer.config_file=${config_path}" } # --- argument parsing ------------------------------------------------------ [[ $# -eq 2 ]] || usage 1 CONFIG_NAME="$1" COMMAND="$2" in_list "${CONFIG_NAME}" "${VALID_CONFIGS[@]}" \ || die "invalid config '${CONFIG_NAME}' (expected: ${VALID_CONFIGS[*]})" in_list "${COMMAND}" "${VALID_COMMANDS[@]}" \ || die "invalid command '${COMMAND}' (expected: ${VALID_COMMANDS[*]})" CONFIG_PATH="${REPO_ROOT}/.devcontainer/${CONFIG_NAME}/devcontainer.json" [[ -f "${CONFIG_PATH}" ]] || die "config not found: ${CONFIG_PATH}" DC_ARGS=(--workspace-folder "${REPO_ROOT}" --config "${CONFIG_PATH}") # --- dispatch -------------------------------------------------------------- case "${COMMAND}" in up) echo ">> bringing up '${CONFIG_NAME}'" devcontainer up "${DC_ARGS[@]}" ;; shell) # Auto-up if not already running. `devcontainer up` is idempotent — # it reuses an existing container, so this is cheap on warm starts. if [[ -z "$(container_id_for "${CONFIG_PATH}")" ]]; then echo ">> '${CONFIG_NAME}' not running, bringing it up first" devcontainer up "${DC_ARGS[@]}" fi echo ">> attaching shell to '${CONFIG_NAME}'" devcontainer exec "${DC_ARGS[@]}" bash 2>/dev/null \ || devcontainer exec "${DC_ARGS[@]}" sh ;; down) cid="$(container_id_for "${CONFIG_PATH}")" if [[ -z "${cid}" ]]; then echo ">> '${CONFIG_NAME}' not running, nothing to stop" exit 0 fi echo ">> stopping '${CONFIG_NAME}'" docker stop "${cid}" ;; rebuild) echo ">> rebuilding '${CONFIG_NAME}' from scratch" devcontainer up "${DC_ARGS[@]}" --remove-existing-container --build-no-cache ;; esac