204 lines
4.9 KiB
Bash
204 lines
4.9 KiB
Bash
#!/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:-<none>}"
|
|
[[ -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."
|