Files
Ansible/dev/Untitled-1.sh

463 lines
16 KiB
Bash
Executable File

#!/usr/bin/env bash
# docker_offline_tool.sh
# Assistant interactif (whiptail) pour préparer un package Docker + docker-compose + images + dockerfiles pour usage hors-ligne.
# Usage: chmod +x docker_offline_tool.sh && ./docker_offline_tool.sh
set -euo pipefail
# ---------- Configuration ----------
WORKDIR="${PWD}/docker_offline_package"
TMPDIR="${WORKDIR}/tmp"
OUT_TARBALL="${PWD}/docker_offline_bundle_$(date +%Y%m%d_%H%M%S).tar.gz"
WHIPTAIL="$(command -v whiptail || true)"
CURL="$(command -v curl || true)"
TAR="$(command -v tar || true)"
DOCKER_CMD="$(command -v docker || true)"
mkdir -p "$WORKDIR" "$TMPDIR" "$WORKDIR/images" "$WORKDIR/dockerfiles" "$WORKDIR/docker_binaries"
if [ -z "$WHIPTAIL" ]; then
echo "Le paquet 'whiptail' est requis. Installez-le (ex: apt install whiptail) puis relancez."
exit 1
fi
if [ -z "$CURL" ] || [ -z "$TAR" ]; then
"$WHIPTAIL" --msgbox "Le script requiert 'curl' et 'tar'." 10 60
exit 1
fi
# ---------- Helpers ----------
info_box() { "$WHIPTAIL" --title "${2:-Info}" --msgbox "$1" 12 70; }
error_box() { "$WHIPTAIL" --title "Erreur" --msgbox "$1" 12 70; }
detect_arch() {
arch=$(uname -m)
case "$arch" in
x86_64) echo "x86_64";;
aarch64|arm64) echo "aarch64";;
armv7l|armv7) echo "armv7";;
ppc64le) echo "ppc64le";;
s390x) echo "s390x";;
*) echo "$arch";;
esac
}
# ---------- Dockerfile templates ----------
create_dockerfile_menu() {
CHOICE=$("$WHIPTAIL" --title "Dockerfile" --menu "Choisir un template ou créer personnalisé" 18 70 8 \
1 "nginx (site statique)" \
2 "node (express sample)" \
3 "python (wheels offline)" \
4 "java (jar)" \
5 "personnalisé" 3>&1 1>&2 2>&3) || return
DST="$WORKDIR/dockerfiles/$(date +%Y%m%d_%H%M%S)_$CHOICE"
mkdir -p "$DST"
case "$CHOICE" in
1)
cat >"$DST/Dockerfile" <<'EOF'
FROM nginx:stable-alpine
COPY ./html /usr/share/nginx/html:ro
EXPOSE 80
CMD ["nginx","-g","daemon off;"]
EOF
;;
2)
cat >"$DST/Dockerfile" <<'EOF'
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
# place npm tarballs or node_modules in ./npm_offline for offline use
RUN npm ci --production || true
COPY . .
EXPOSE 3000
CMD ["node","server.js"]
EOF
;;
3)
cat >"$DST/Dockerfile" <<'EOF'
FROM python:3.11-slim
WORKDIR /app
# place wheels in ./wheels
COPY ./wheels ./wheels
COPY requirements.txt .
RUN pip install --no-index --find-links=./wheels -r requirements.txt
COPY . .
CMD ["python","app.py"]
EOF
;;
4)
cat >"$DST/Dockerfile" <<'EOF'
FROM eclipse-temurin:17-jre-jammy
WORKDIR /app
COPY app.jar ./app.jar
EXPOSE 8080
CMD ["java","-jar","/app/app.jar"]
EOF
;;
5)
BASE=$("$WHIPTAIL" --inputbox "Image de base (ex: debian:bookworm)" 10 60 "debian:bookworm" 3>&1 1>&2 2>&3)
RUNS=$("$WHIPTAIL" --inputbox "Commandes RUN (séparées par &&) - ex: apt update && apt install -y curl" 12 60 "apt update && apt install -y curl" 3>&1 1>&2 2>&3)
START=$("$WHIPTAIL" --inputbox "Commande de démarrage (CMD) - ex: /bin/bash" 8 60 "/bin/bash" 3>&1 1>&2 2>&3)
cat >"$DST/Dockerfile" <<EOF
FROM ${BASE}
WORKDIR /app
COPY . .
RUN ${RUNS}
CMD ["${START}"]
EOF
;;
esac
info_box "Dockerfile créé dans : $DST"
}
# ---------- Build image (modes progress / console) ----------
build_image_prepare_offline() {
# choose display mode
DISPLAY=$("$WHIPTAIL" --title "Mode d'affichage" --menu "Choisissez le mode" 14 70 6 \
progress "Barre de progression (étapes)" \
console "Console dynamique (logs en direct)" 3>&1 1>&2 2>&3) || return
# collect Dockerfile choice
DF_CHOICES=()
for d in "$WORKDIR"/dockerfiles/*; do
[ -d "$d" ] || continue
DF_CHOICES+=("$d" "$(basename "$d")")
done
DF_CHOICES+=("Autre" "Choisir un dossier manuellement")
DF_SELECTED=$("$WHIPTAIL" --title "Sélection Dockerfile" --menu "Choisir" 18 70 12 "${DF_CHOICES[@]}" 3>&1 1>&2 2>&3) || return
if [ "$DF_SELECTED" = "Autre" ]; then
DF_SELECTED=$("$WHIPTAIL" --fselect "$PWD/" 20 70 3>&1 1>&2 2>&3) || return
fi
if [ ! -f "$DF_SELECTED/Dockerfile" ]; then
error_box "Aucun Dockerfile trouvé dans $DF_SELECTED"
return
fi
IMAGE_NAME=$("$WHIPTAIL" --inputbox "Nom de l'image (ex: myapp:offline)" 10 60 "myapp:offline" 3>&1 1>&2 2>&3) || return
# include extra dependencies (local or URL)
if "$WHIPTAIL" --yesno "Inclure des fichiers/dépendances dans le contexte (wheels, npm tarballs, .deb/.rpm, binaires) ?" 10 60; then
EXTRA_FILES=()
while true; do
entry=$("$WHIPTAIL" --inputbox "Chemin local ou URL (laisser vide pour terminer)" 10 70 "" 3>&1 1>&2 2>&3) || break
[ -z "$entry" ] && break
EXTRA_FILES+=("$entry")
done
else
EXTRA_FILES=()
fi
# prepare context
CONTEXT_DIR="$TMPDIR/build_ctx_$(date +%s)"
rm -rf "$CONTEXT_DIR"
mkdir -p "$CONTEXT_DIR"
cp -r "$DF_SELECTED"/* "$CONTEXT_DIR/" 2>/dev/null || true
# download/copy extras with gauge
for f in "${EXTRA_FILES[@]:-}"; do
if [[ "$f" =~ ^https?:// ]]; then
BASENAME="$(basename "$f")"
# download with curl and show progress in gauge by polling bytes
TEMP_DL="$CONTEXT_DIR/${BASENAME}"
# Use curl --progress-bar and parse percentages - simpler to show a small gauge animation
(
echo "0"; echo "# Téléchargement: $BASENAME"
if ! curl -L --fail -o "$TEMP_DL" "$f" 2>/tmp/curl_err; then
echo "0"; echo "# Erreur téléchargement: $BASENAME (voir /tmp/curl_err)"; sleep 1
else
echo "100"; echo "# Téléchargement terminé: $BASENAME"
fi
) | "$WHIPTAIL" --gauge "Téléchargement..." 10 70 0
else
if [ -e "$f" ]; then
cp -r "$f" "$CONTEXT_DIR/" || true
else
(echo "0"; echo "# Fichier introuvable: $f") | "$WHIPTAIL" --gauge "Attention" 8 60 0
fi
fi
done
# check docker present for local build
if [ -z "$DOCKER_CMD" ]; then
if ! "$WHIPTAIL" --yesno "docker n'est pas installé sur la machine préparatrice. Voulez-vous seulement préparer le contexte et sauvegarder (sans builder) ?" 10 70; then
info_box "Le build local nécessite docker. Installez docker ou choisissez 'préparer seulement'."
return
else
# just prepare context and return
info_box "Contexte préparé dans $CONTEXT_DIR. Vous pouvez transférer ce dossier vers une machine avec docker pour build."
return
fi
fi
LOG="$TMPDIR/docker_build_$(date +%s).log"
: >"$LOG"
# run docker build in background
# capture build progress by writing to logfile
(
echo "=== Build started: $(date) ===" >>"$LOG"
if ! docker build -t "$IMAGE_NAME" "$CONTEXT_DIR" >>"$LOG" 2>&1; then
echo "=== Build FAILED at $(date) ===" >>"$LOG"
exit 2
fi
echo "=== Build success: $(date) ===" >>"$LOG"
echo "=== Saving image to tar ===" >>"$LOG"
mkdir -p "$WORKDIR/images"
if ! docker save -o "$WORKDIR/images/$(echo "$IMAGE_NAME" | tr '/:' '__').tar" "$IMAGE_NAME" >>"$LOG" 2>&1; then
echo "=== Save FAILED: $(date) ===" >>"$LOG"
exit 3
fi
echo "=== Image saved ===" >>"$LOG"
) &
BUILD_PID=$!
# show progress or console
if [ "$DISPLAY" = "progress" ]; then
# staged progress: 0->70 build, 70->95 save, 95->100 finalize
pct=0
while kill -0 "$BUILD_PID" 2>/dev/null; do
# estimate: if logfile contains "Step X/X", try to infer progress
steps_done=$(grep -oE "Step [0-9]+/[0-9]+" "$LOG" | tail -n1 | grep -oE "[0-9]+/[0-9]+" || true)
if [ -n "$steps_done" ]; then
cur=$(echo "$steps_done" | cut -d/ -f1)
tot=$(echo "$steps_done" | cut -d/ -f2)
# scale to 0..70
pct=$(( 1 + (cur * 69 / (tot == 0 ? 1 : tot)) ))
else
# fallback: increase slowly to show activity
pct=$(( (pct + 3) % 65 + 1 ))
fi
# show last lines as message
tail_msg=$(tail -n 6 "$LOG" 2>/dev/null || true)
{
echo "$pct"
echo "# Build en cours..."
echo "$tail_msg"
} | "$WHIPTAIL" --gauge "Construction: $IMAGE_NAME" 15 70 "$pct"
sleep 1
done
# After process ends, show final gauge to 95..100 while saving maybe
wait "$BUILD_PID" || RC=$? || true
# finalize
{
echo "95"; echo "# Finalisation..."
sleep 1
echo "100"; echo "# Terminé"
} | "$WHIPTAIL" --gauge "Finalisation..." 8 60 0
# show full log
"$WHIPTAIL" --title "Log build" --textbox "$LOG" 25 90
else
# console mode: show last lines live inside a repeated tailbox (keeps whiptail visible)
# We'll show a live-updating gauge that contains last lines (works similarly to console).
while kill -0 "$BUILD_PID" 2>/dev/null; do
tail_msg=$(tail -n 20 "$LOG" 2>/dev/null || true)
# compute a visual percentage heuristic: presence of "Saving" or "Successfully built"
if grep -q "Saving" "$LOG" 2>/dev/null || grep -q "Image is up to date" "$LOG" 2>/dev/null; then
pct=85
elif grep -q "Successfully built" "$LOG" 2>/dev/null || grep -q "BUILD SUCCESS" "$LOG" 2>/dev/null; then
pct=70
else
pct=30
fi
{
echo "$pct"
echo "# Logs (mise à jour automatique):"
echo "$tail_msg"
} | "$WHIPTAIL" --gauge "Build live: $IMAGE_NAME (mode console)" 20 90 "$pct"
sleep 1
done
wait "$BUILD_PID" || true
"$WHIPTAIL" --title "Log build complet" --textbox "$LOG" 25 90
fi
# check final result
if grep -q "Build FAILED" "$LOG" 2>/dev/null || grep -q "FAILED" "$LOG" 2>/dev/null; then
error_box "Le build a échoué. Consultez le log complet."
else
info_box "Image buildée et sauvegardée dans : $WORKDIR/images"
fi
}
# ---------- Prepare offline docker + compose package ----------
prepare_docker_offline_installer() {
arch_default=$(detect_arch)
ARCH=$("$WHIPTAIL" --inputbox "Architecture cible (détectée: $arch_default)" 10 60 "$arch_default" 3>&1 1>&2 2>&3) || return
# ask Docker static version or latest
DOCKER_VER=$("$WHIPTAIL" --inputbox "Version Docker static (laisser vide pour latest disponible sur download.docker.com)" 10 70 "" 3>&1 1>&2 2>&3) || return
BIN_DIR="$WORKDIR/docker_binaries"
mkdir -p "$BIN_DIR"
# fetch docker static tarball
index_url="https://download.docker.com/linux/static/stable/${ARCH}/"
if [ -z "$DOCKER_VER" ]; then
# try to fetch listing and get latest docker-*.tgz
listing=$($CURL -fsSL "$index_url" || true)
# find docker-<version>.tgz - fallback to first match
docker_tgz=$(echo "$listing" | grep -oE 'docker-[0-9]+\.[0-9]+\.[0-9]+\.tgz' | sort -V | tail -n1 || true)
if [ -z "$docker_tgz" ]; then
# fallback generic
error_box "Impossible de détecter automatiquement la version Docker sur $index_url. Indique manuellement le nom du fichier ou une version."
DOCKER_VER=$("$WHIPTAIL" --inputbox "Spécifie le nom exact du tarball (ex: docker-24.0.5.tgz)" 10 70 "" 3>&1 1>&2 2>&3) || return
docker_tgz="$DOCKER_VER"
fi
else
docker_tgz="$DOCKER_VER"
fi
docker_url="${index_url}${docker_tgz}"
# download with progress gauge
(
echo "0"; echo "# Téléchargement $docker_tgz ..."
if $CURL -fSL -o "$BIN_DIR/$docker_tgz" "$docker_url" 2>/tmp/docker_dl_err; then
echo "80"; echo "# Téléchargement terminé"
else
echo "0"; echo "# Erreur téléchargement: $docker_url (voir /tmp/docker_dl_err)"
"$WHIPTAIL" --msgbox "Erreur téléchargement Docker static. Vérifiez l'URL: $docker_url\nVoir /tmp/docker_dl_err" 12 70
return
fi
echo "90"; echo "# Extraction test..."
sleep 1
echo "100"; echo "# OK"
) | "$WHIPTAIL" --gauge "Téléchargement des binaires Docker..." 12 70 0
# docker-compose plugin (CLI) - ask version or latest
COMPOSE_VER=$("$WHIPTAIL" --inputbox "Version docker-compose plugin (ex: v2.24.1) - laisser vide pour latest" 10 70 "" 3>&1 1>&2 2>&3) || return
if [ -z "$COMPOSE_VER" ]; then
# query GitHub API for latest release assets
api_json=$($CURL -fsSL "https://api.github.com/repos/docker/compose/releases/latest" || true)
compose_asset=$(
echo "$api_json" | grep -oP '"browser_download_url":\s*"\K[^"]*' | grep "docker-compose-linux-${ARCH}" | head -n1 || true
)
if [ -z "$compose_asset" ]; then
# try generic naming
compose_asset=$(
echo "$api_json" | grep -oP '"browser_download_url":\s*"\K[^"]*' | grep "docker-compose-linux" | head -n1 || true
)
fi
else
# build download url
case "$ARCH" in
x86_64) arch_name="x86_64";;
aarch64) arch_name="aarch64";;
armv7) arch_name="armv7";;
*) arch_name="$ARCH";;
esac
compose_asset="https://github.com/docker/compose/releases/download/${COMPOSE_VER}/docker-compose-linux-${arch_name}"
fi
if [ -z "$compose_asset" ]; then
error_box "Impossible de déterminer l'asset docker-compose à télécharger. Donne une version manuellement."
return
fi
(
echo "0"; echo "# Téléchargement docker-compose..."
if $CURL -fSL -o "$BIN_DIR/docker-compose" "$compose_asset" 2>/tmp/compose_dl_err; then
chmod +x "$BIN_DIR/docker-compose"
echo "100"; echo "# docker-compose OK"
else
echo "0"; echo "# Erreur téléchargement docker-compose (voir /tmp/compose_dl_err)"
"$WHIPTAIL" --msgbox "Erreur téléchargement docker-compose: $compose_asset\nVoir /tmp/compose_dl_err" 12 70
return
fi
) | "$WHIPTAIL" --gauge "Téléchargement docker-compose..." 10 70 0
# create installer script for offline server
INSTALLER="$WORKDIR/install_docker_offline.sh"
cat >"$INSTALLER" <<'INSTALLER_EOF'
#!/usr/bin/env bash
set -euo pipefail
cd "$(dirname "$0")"
# Install static docker binaries included in this package
if [ "$(id -u)" -ne 0 ]; then
echo "Exécutez en root: sudo ./install_docker_offline.sh"
exit 1
fi
echo "Installation des binaires Docker depuis le package..."
# find docker-*.tgz
shopt -s nullglob
for t in docker_binaries/docker-*.tgz; do
echo "Extraction $t ..."
tar xzf "$t" -C /tmp/docker_offline_extract || true
# copy binaries (best-effort)
DIR=$(ls -d /tmp/docker_offline_extract/* 2>/dev/null | head -n1 || true)
if [ -n "$DIR" ]; then
cp "$DIR"/* /usr/bin/ 2>/dev/null || true
chmod +x /usr/bin/docker* /usr/bin/dockerd* /usr/bin/containerd* 2>/dev/null || true
fi
done
# install docker-compose plugin if present
if [ -f docker_binaries/docker-compose ]; then
mkdir -p /usr/libexec/docker/cli-plugins 2>/dev/null || true
cp docker_binaries/docker-compose /usr/libexec/docker/cli-plugins/docker-compose 2>/dev/null || true
chmod +x /usr/libexec/docker/cli-plugins/docker-compose 2>/dev/null || true
fi
# Load saved images
if [ -d images ]; then
for img in images/*.tar; do
[ -f "$img" ] || continue
echo "Chargement image $img ..."
docker load -i "$img" || true
done
fi
echo "Installation offline terminée. Vérifiez 'docker --version' et 'docker compose version'."
INSTALLER_EOF
chmod +x "$INSTALLER"
# assemble lightweight package structure
PKG_DIR="$WORKDIR/offline_bundle"
rm -rf "$PKG_DIR"
mkdir -p "$PKG_DIR/docker_binaries" "$PKG_DIR/images" "$PKG_DIR/dockerfiles"
cp -a "$WORKDIR/docker_binaries/"* "$PKG_DIR/docker_binaries/" 2>/dev/null || true
cp -a "$WORKDIR/images/"* "$PKG_DIR/images/" 2>/dev/null || true
cp -a "$WORKDIR/dockerfiles/"* "$PKG_DIR/dockerfiles/" 2>/dev/null || true
cp "$INSTALLER" "$PKG_DIR/"
# create tarball
(
echo "0"; echo "# Compression du bundle..."
sleep 1
tar czf "$OUT_TARBALL" -C "$PKG_DIR" .
echo "100"; echo "# Bundle créé"
) | "$WHIPTAIL" --gauge "Création du bundle offline..." 10 70 0
info_box "Bundle prêt" "Bundle créé : $OUT_TARBALL\nTransférez-le sur la machine hors-ligne et exécutez install_docker_offline.sh en root."
}
# ---------- Assemble full bundle (if needed) ----------
assemble_bundle() {
PKG_DIR="$WORKDIR/offline_bundle"
if [ ! -d "$PKG_DIR" ]; then
error_box "Aucun bundle préparé. Lancez d'abord la préparation (Préparer Docker offline)."
return
fi
tar czf "$OUT_TARBALL" -C "$PKG_DIR" .
info_box "Archive créée" "Archive: $OUT_TARBALL"
}
# ---------- Menu principal ----------
main_menu() {
while true; do
CHOICE=$("$WHIPTAIL" --title "Docker Offline Tool" --menu "Que voulez-vous faire ?" 18 70 10 \
1 "Créer / builder image Docker (préparer offline)" \
2 "Préparer package d'installation Docker + Compose (offline)" \
3 "Créer Dockerfile (templates ou perso)" \
4 "Assembler bundle final (.tar.gz)" \
5 "Quitter" 3>&1 1>&2 2>&3) || break
case "$CHOICE" in
1) build_image_prepare_offline ;;
2) prepare_docker_offline_installer ;;
3) create_dockerfile_menu ;;
4) assemble_bundle ;;
5) break ;;
*) error_box "Choix invalide" ;;
esac
done
}
main_menu