#!/usr/bin/env bash # # devcontainer.sh — devcontainer helper for this repo # # Usage: # ./devcontainer.sh # # Commands: # up build + start the devcontainer (idempotent) # shell attach a bash shell; auto-ups if not running # down stop the devcontainer # rebuild remove + rebuild from scratch, no cache # # Examples: # ./devcontainer.sh shell # one-shot: up if needed, then bash # ./devcontainer.sh rebuild set -euo pipefail SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" &>/dev/null && pwd)" REPO_ROOT="${SCRIPT_DIR}" CONFIG_PATH="${REPO_ROOT}/.devcontainer/devcontainer.json" VALID_COMMANDS=(up shell down rebuild) usage() { sed -n '3,15p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//' exit "${1:-0}" } die() { echo "error: $*" >&2 exit 1 } in_list() { local needle="$1"; shift local item for item in "$@"; do [[ "${item}" == "${needle}" ]] && return 0 done return 1 } container_id() { # Find the running container for this repo via devcontainer labels. docker ps -q \ --filter "label=devcontainer.local_folder=${REPO_ROOT}" \ --filter "label=devcontainer.config_file=${CONFIG_PATH}" } [[ $# -eq 1 ]] || usage 1 COMMAND="$1" in_list "${COMMAND}" "${VALID_COMMANDS[@]}" \ || die "invalid command '${COMMAND}' (expected: ${VALID_COMMANDS[*]})" [[ -f "${CONFIG_PATH}" ]] || die "config not found: ${CONFIG_PATH}" DC_ARGS=(--workspace-folder "${REPO_ROOT}") case "${COMMAND}" in up) echo ">> bringing up devcontainer" devcontainer up "${DC_ARGS[@]}" ;; shell) if [[ -z "$(container_id)" ]]; then echo ">> devcontainer not running, bringing it up first" devcontainer up "${DC_ARGS[@]}" fi echo ">> attaching shell" devcontainer exec "${DC_ARGS[@]}" bash 2>/dev/null \ || devcontainer exec "${DC_ARGS[@]}" sh ;; down) cid="$(container_id)" if [[ -z "${cid}" ]]; then echo ">> devcontainer not running, nothing to stop" exit 0 fi echo ">> stopping devcontainer" docker stop "${cid}" ;; rebuild) echo ">> rebuilding devcontainer from scratch" devcontainer up "${DC_ARGS[@]}" --remove-existing-container --build-no-cache ;; esac