Files
Linux_post_install/bin/pos-docker-vbox
T
2026-08-24 14:44:25 -04:00

262 lines
7.5 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
# POS: docker vbox — Disposable Docker-based VMs (create/enter/start/stop/rm/ls)
# POS_SUBCMDS: create enter stop start rm ls menu
source "$(dirname "$0")/../lib/common.sh" 2>/dev/null || source "$(dirname "$0")/common.sh"
source "$(dirname "$0")/../lib/menu-lib.sh" 2>/dev/null || source "$(dirname "$0")/menu-lib.sh"
usage() {
cat <<EOF
Usage:
pos docker vbox create <name> [image] [--dir <path>]
pos docker vbox enter <name>
pos docker vbox stop <name>
pos docker vbox start <name>
pos docker vbox rm <name>
pos docker vbox ls
Manage disposable Docker containers as lightweight VMs.
Bare \`pos docker vbox\` on a terminal (or \`pos docker vbox menu\`) opens an
interactive menu wrapping these verbs; arguments stay scriptable.
Each container gets a bind-mounted host directory so files persist
on the host even after the container is removed.
Options:
--dir <path> Use custom directory instead of default ~/<name>
Use "." for current directory
Examples:
pos docker vbox create lab1
pos docker vbox create lab1 --dir .
pos docker vbox create lab1 --dir /mnt/data/lab1
pos docker vbox create kali kalilinux/kali-rolling
pos docker vbox enter lab1
pos docker vbox stop lab1
pos docker vbox start lab1
pos docker vbox rm lab1
pos docker vbox ls
EOF
exit 0
}
# ── Interactive menu (opt-in front door, Pattern B via lib/menu-lib.sh) ──
# Every item re-enters the existing CLI verbs below unchanged — the menu adds
# picks/prompts/confirms only (same self-invocation the tool itself uses for
# tmux handovers). 'enter' hands the terminal to the container shell and
# returns to the loop when the shell exits.
menu_self() { # run this tool's own verb as a child process
"$(cd "$(dirname "$0")" && pwd)/$(basename "$0")" "$@"
}
menu_list_vms() { # vbox VM names, one per line (same label filter as 'ls')
docker ps -a --filter label=linux_post_install.vbox=true --format '{{.Names}}' | LC_ALL=C sort
}
menu_pick_vm() { # $1 = prompt → picked VM name on stdout · rc 1 = cancelled / none
local -a vms=()
mapfile -t vms < <(menu_list_vms 2>/dev/null)
if [ ${#vms[@]} -eq 0 ]; then
if docker info &>/dev/null; then
warn "no vbox VMs found — create one first ('pos docker vbox create <name>')"
else
warn "docker daemon not reachable — start Docker first"
fi
return 1
fi
local idx
idx="$(menu_pick "$1" "${vms[@]}")" || return 1
printf '%s\n' "${vms[$((idx - 1))]}"
}
menu_vbox_create() {
local name image dir
name="$(menu_ask_value "VM name")" || return 0
image="$(menu_ask_value "Image" "ubuntu:22.04")" || return 0
dir="$(menu_ask_value "Host directory (empty = ~/$name)")" || true
if [ -n "${dir:-}" ]; then
confirm "Create VM '$name' from $image (host dir: $dir)?" n \
|| { log "Cancelled"; return 0; }
menu_self create "$name" "$image" --dir "$dir"
else
confirm "Create VM '$name' from $image?" n || { log "Cancelled"; return 0; }
menu_self create "$name" "$image"
fi
}
menu_vbox_enter() {
local vm
vm="$(menu_pick_vm "Enter which VM?")" || return 0
log "Entering '$vm' — exit its shell to return to the menu"
menu_self enter "$vm"
}
menu_vbox_verb() { # $1 = start|stop — pick a VM, run the verb
local verb="$1" vm
vm="$(menu_pick_vm "${verb} which VM?")" || return 0
menu_self "$verb" "$vm"
}
menu_vbox_remove() {
local vm
vm="$(menu_pick_vm "Remove which VM?")" || return 0
confirm "Permanently remove VM '$vm' (docker rm -f; the host folder is kept)?" n \
|| { log "Cancelled — '$vm' kept"; return 0; }
menu_self rm "$vm"
}
run_menu() {
menu_guard || exit 1
while true; do
local choice
choice="$(menu_run "Docker VBox — disposable VMs" \
"List VMs" \
"Create a VM" \
"Enter a VM (interactive shell)" \
"Start a VM" \
"Stop a VM" \
"Remove a VM (permanent)")" || return 0
case "$choice" in
1) menu_self ls ;;
2) menu_vbox_create ;;
3) menu_vbox_enter ;;
4) menu_vbox_verb start ;;
5) menu_vbox_verb stop ;;
6) menu_vbox_remove ;;
esac
done
}
# Menu door: explicit verb, or zero args on a terminal. Everything below —
# including zero args without a terminal — stays byte-compatible with the
# pre-menu CLI.
if [ "${1:-}" = "menu" ]; then
run_menu
exit 0
fi
if [ $# -eq 0 ] && [ -t 0 ]; then
run_menu
exit 0
fi
case "${1:-}" in
-h|--help) usage ;;
esac
cmd="${1:-}"
[ -z "$cmd" ] && usage
container_exists() {
docker container inspect "$1" &>/dev/null
}
container_running() {
[[ "$(docker inspect -f '{{.State.Running}}' "$1" 2>/dev/null)" == "true" ]]
}
case "$cmd" in
create)
name="${2:-}"
[ -z "$name" ] && usage
# Parse remaining args: [image] [--dir <path>]
image="ubuntu:22.04"
custom_dir=""
shift 2 || true
while [ $# -gt 0 ]; do
case "$1" in
--dir)
[ -z "${2:-}" ] && { echo "Missing value for --dir"; exit 1; }
custom_dir="$2"
shift 2
;;
*)
image="$1"
shift
;;
esac
done
if container_exists "$name"; then
echo "[!] Container already exists: $name"
exit 0
fi
if [ -n "$custom_dir" ]; then
lab_dir="$(cd "$custom_dir" 2>/dev/null && pwd)" || { echo "[!] Directory not found: $custom_dir"; exit 1; }
else
lab_dir="$HOME/$name"
fi
mkdir -p "$lab_dir"
echo "[+] Lab directory: $lab_dir"
echo "[+] Pulling image: $image"
docker pull "$image"
echo "[+] Creating: $name"
docker create \
-it \
--name "$name" \
--label linux_post_install.vbox=true \
-v "$lab_dir:$lab_dir" \
-w "$lab_dir" \
"$image" \
bash >/dev/null
echo "[+] Done"
if confirm "Enter now?"; then
docker start "$name" >/dev/null
exec docker exec -it -w "$lab_dir" "$name" bash
fi
;;
enter)
name="${2:-}"
[ -z "$name" ] && usage
if ! container_exists "$name"; then
echo "[!] Container not found: $name"
exit 1
fi
if ! container_running "$name"; then
docker start "$name" >/dev/null
fi
# Detect working dir from container mounts
lab_dir=$(docker inspect -f '{{range .Mounts}}{{if eq .Destination .Destination}}{{.Source}}{{end}}{{end}}' "$name" 2>/dev/null | head -1)
if [ -n "$lab_dir" ] && [ -d "$lab_dir" ]; then
exec docker exec -it -w "$lab_dir" "$name" bash
else
exec docker exec -it "$name" bash
fi
;;
start)
name="${2:-}"
[ -z "$name" ] && usage
docker start "$name"
;;
stop)
name="${2:-}"
[ -z "$name" ] && usage
docker stop "$name"
;;
rm)
name="${2:-}"
[ -z "$name" ] && usage
docker rm -f "$name"
;;
ls)
docker ps -a --filter label=linux_post_install.vbox=true --format "table {{.Names}}\t{{.Image}}\t{{.Status}}"
;;
*)
usage
;;
esac