sebys-hosting-scripts/temp-env.sh

205 lines
5 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"
_____ _____ ___
|_ _|__ _ __ ___ _ __ | ___|_ ____ __ / _ \
| |/ _ \ '_ ` _ \| '_ \ ____| |_ | '_ \ \ / /| | | |
| | __/ | | | | | |_) |____| _|| | | \ V / | |_| |
|_|\___|_| |_| |_| .__/ |_| |_| |_|\_/ \___/
|_|
Ephemeral Debian Env | 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."