#!/usr/bin/env bash set -euo pipefail ######################################## # Seby's Ephemeral Debian Env # curl | bash safe ######################################## ######################################## # Styling ######################################## TTY="/dev/tty" BOLD="\033[1m" GREEN="\033[32m" YELLOW="\033[33m" RED="\033[31m" BLUE="\033[34m" RESET="\033[0m" section() { printf "\n${BLUE}${BOLD}==> %s${RESET}\n" "$*"; } say() { printf "${GREEN}✔ %s${RESET}\n" "$*"; } warn() { printf "${YELLOW}⚠ %s${RESET}\n" "$*"; } err() { printf "${RED}✖ %s${RESET}\n" "$*"; } have() { command -v "$1" >/dev/null 2>&1; } need_tty() { if [[ ! -r "$TTY" || ! -w "$TTY" ]]; then err "No interactive TTY available." exit 1 fi } prompt() { local msg="$1" local default="${2:-}" local out if [[ -n "$default" ]]; then printf "${BOLD}%s${RESET} " "$msg [$default]:" >"$TTY" else printf "${BOLD}%s${RESET} " "$msg:" >"$TTY" fi IFS= read -r out <"$TTY" printf "%s" "${out:-$default}" } confirm_default_no() { local msg="$1" local ans ans="$(prompt "$msg [y/N]")" [[ "${ans,,}" == "y" || "${ans,,}" == "yes" ]] } confirm_default_yes() { local msg="$1" local ans ans="$(prompt "$msg [Y/n]" "y")" [[ "${ans,,}" == "y" || "${ans,,}" == "yes" ]] } ######################################## # Spinner (writes to /dev/tty so curl|bash stays safe) ######################################## run_with_spinner() { local label="$1"; shift ("$@") & local pid=$! local spin='|/-\' local i=0 printf "\033[?25l" >"$TTY" || true while kill -0 "$pid" 2>/dev/null; do i=$(( (i + 1) % 4 )) printf "\r${BLUE}${BOLD}..${RESET} %s %s" "${spin:$i:1}" "$label" >"$TTY" sleep 0.12 done wait "$pid" local rc=$? printf "\r\033[K" >"$TTY" printf "\033[?25h" >"$TTY" || true return "$rc" } ######################################## # Header ######################################## clear cat << "EOF" ______________ _______ _______ ___ __ /_ __/ ____/ |/ / __ \ / ____/ | / / | / / / / / __/ / /|_/ / /_/ / / __/ / |/ /| | / / / / / /___/ / / / ____/ / /___/ /| / | |/ / /_/ /_____/_/ /_/_/ /_____/_/ |_/ |___/ Temp Debian Environment | v1.0 EOF need_tty if ! have docker; then err "Docker not found. Install it first, then re-run this script." exit 1 fi say "Docker detected: $(docker --version)" ######################################## # Configure ######################################## section "Configuration" TAG="$(prompt "Debian tag" "bookworm")" IMAGE="debian:${TAG}" PACKAGES="$(prompt "Packages to install (space-separated, blank for none)")" APT_FLAGS="$(prompt "Extra apt-get flags (e.g. --no-install-recommends)")" MOUNT_DIR="$(prompt "Mount a local directory into the container (path or blank)")" MOUNT_ARGS=() if [[ -n "$MOUNT_DIR" ]]; then if [[ -d "$MOUNT_DIR" ]]; then MOUNT_ARGS=(-v "$(realpath "$MOUNT_DIR"):/mnt/host") say "Will mount $(realpath "$MOUNT_DIR") -> /mnt/host" else warn "Directory '$MOUNT_DIR' doesn't exist — skipping mount." MOUNT_DIR="" fi fi RM_IMAGE=false if confirm_default_no "Remove image (${IMAGE}) after exiting, not just the container?"; then RM_IMAGE=true fi if [[ -n "$PACKAGES" ]]; then INNER_CMD="apt-get update && apt-get install -y ${APT_FLAGS} ${PACKAGES} && bash" else INNER_CMD="apt-get update && bash" fi ######################################## # Summary ######################################## section "Summary" say "Image: ${IMAGE}" say "Packages: ${PACKAGES:-}" [[ -n "$MOUNT_DIR" ]] && say "Mount: $(realpath "$MOUNT_DIR") -> /mnt/host" say "Cleanup: container removed on exit$([[ "$RM_IMAGE" == true ]] && echo ", image removed too")" if ! confirm_default_yes "Launch now?"; then warn "Aborted." exit 0 fi ######################################## # Pull image (quiet + spinner) ######################################## section "Pulling image" if run_with_spinner "Pulling ${IMAGE}..." docker pull "$IMAGE" >/dev/null 2>&1; then say "Image ready: ${IMAGE}" else err "Failed to pull ${IMAGE}" exit 1 fi ######################################## # Launch ######################################## section "Launching ephemeral environment" say "Type 'exit' when you're done — the container cleans up automatically." set +e docker run --rm -it "${MOUNT_ARGS[@]}" "$IMAGE" bash -c "$INNER_CMD" <"$TTY" EXIT_CODE=$? set -e section "Done" say "Container exited (code ${EXIT_CODE}) and was automatically removed." ######################################## # Optional image cleanup ######################################## if [[ "$RM_IMAGE" == true ]]; then section "Image cleanup" IN_USE="$(docker ps -a --filter ancestor="$IMAGE" -q)" if [[ -n "$IN_USE" ]]; then warn "Other containers still reference ${IMAGE} — skipping removal:" docker ps -a --filter ancestor="$IMAGE" else docker rmi "$IMAGE" >/dev/null 2>&1 && say "Removed image ${IMAGE}." fi fi say "All done."