Files
streambox-install/install.sh
T

256 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
# =============================================================================
# streambox — installation / mise à jour en une ligne
#
# Rapide (image déjà présente sur la machine, ex. après un `docker load`) :
# curl -fsSL https://git.hilel.fr/hamriou/streambox-install/raw/branch/main/install.sh | sudo bash
#
# Depuis une archive :
# curl -fsSL .../install.sh | sudo bash -s -- --tar /tmp/streambox-aio-v49.tar --tag v49
#
# Depuis une autre machine du LAN (docker save | docker load, sans registry) :
# curl -fsSL .../install.sh | sudo bash -s -- --from-ssh lab --tag v49
#
# Aucun registry, aucun token, aucun secret. Le compose vient de ce dépôt public.
# =============================================================================
set -euo pipefail
BASE_URL="${BASE_URL:-https://git.hilel.fr/hamriou/streambox-install/raw/branch/main}"
INSTALL_DIR="${INSTALL_DIR:-$HOME/aio}"
IMAGE_TAG="${IMAGE_TAG:-latest}"
IMAGE_REPO="${IMAGE_REPO:-streambox-aio}"
UI_PORT="${UI_PORT:-3005}"
SRC_TAR=""
SRC_REF=""
SRC_SSH=""
DRY=0
WAIT="${WAIT:-90}"
DOCKER_MODE="${DOCKER_MODE:-ask}" # ask | auto | manual
log() { printf '\033[1;34m==>\033[0m %s\n' "$*"; }
ok() { printf '\033[1;32m ok\033[0m %s\n' "$*"; }
warn() { printf '\033[1;33m !!\033[0m %s\n' "$*" >&2; }
die() { printf '\033[1;31mERREUR:\033[0m %s\n' "$*" >&2; exit 1; }
usage() {
cat <<'EOF'
streambox — installeur
--dir DIR répertoire d'installation (défaut: ~/aio)
--tag TAG tag de l'image (défaut: latest)
--image REPO:TAG tirer l'image depuis un registry
--tar FICHIER charger l'image depuis une archive (docker load), chemin ou URL
--from-ssh HOST charger l'image depuis HOST (ssh HOST 'docker save ...' | docker load)
--wait SECONDES attente max du health check (défaut: 90)
--install-docker installer Docker automatiquement si absent (sans question)
--no-install-docker ne jamais installer Docker (quitte avec la commande à lancer)
--dry-run afficher les actions sans rien modifier
-h, --help cette aide
Sans --image / --tar / --from-ssh, l'installeur utilise l'image locale
streambox-aio:<tag> si elle existe déjà (cas d'une mise à jour).
EOF
}
while [ $# -gt 0 ]; do
case "$1" in
--dir) INSTALL_DIR="${2:?}"; shift 2 ;;
--tag) IMAGE_TAG="${2:?}"; shift 2 ;;
--image) SRC_REF="${2:?}"; shift 2 ;;
--tar) SRC_TAR="${2:?}"; shift 2 ;;
--from-ssh) SRC_SSH="${2:?}"; shift 2 ;;
--wait) WAIT="${2:?}"; shift 2 ;;
--install-docker) DOCKER_MODE=auto; shift ;;
--no-install-docker) DOCKER_MODE=manual; shift ;;
--dry-run) DRY=1; shift ;;
-h|--help) usage; exit 0 ;;
*) die "argument inconnu: $1 (voir --help)" ;;
esac
done
FULL_IMAGE="$IMAGE_REPO:$IMAGE_TAG"
COMPOSE="$INSTALL_DIR/docker-compose.yml"
# ----------------------------------------------------------------- 1. prérequis
log "Prérequis"
COMMANDE_DOCKER="curl -fsSL https://get.docker.com | sudo sh"
installer_docker() {
log "Installation automatique de Docker (script officiel get.docker.com)"
curl -fsSL https://get.docker.com | sh || die "l'installation de Docker a échoué"
hash -r
command -v docker >/dev/null 2>&1 || die "docker reste absent après l'installation"
docker info >/dev/null 2>&1 || die "Docker installé mais le démon ne répond pas (systemctl start docker)"
docker compose version >/dev/null 2>&1 || die "docker compose v2 absent après l'installation"
ok "$(docker --version | cut -d, -f1)"
}
# Demande quoi faire quand Docker (ou son greffon compose) manque.
# Lit sur /dev/tty : avec `curl | bash`, stdin contient le script, pas le clavier.
resoudre_docker() {
local raison="$1"
warn "$raison"
case "$DOCKER_MODE" in
auto) installer_docker; return 0 ;;
manual) die "installation manuelle demandée. Lance : $COMMANDE_DOCKER" ;;
esac
if ! ( : > /dev/tty ) 2>/dev/null; then
die "pas de terminal interactif. Relance avec --install-docker, ou lance : $COMMANDE_DOCKER"
fi
{
printf '\n %s\n\n' "$raison"
printf ' 1) Installer Docker automatiquement (script officiel get.docker.com)\n'
printf ' 2) Quitter et installer Docker manuellement\n\n'
printf ' Choix [1/2] : '
} > /dev/tty
local rep=""
read -r rep < /dev/tty || rep="2"
case "$rep" in
1) installer_docker ;;
*) printf '\n Installation manuelle :\n %s\n\n' "$COMMANDE_DOCKER" > /dev/tty
die "arrêt à la demande de l'utilisateur" ;;
esac
}
command -v curl >/dev/null 2>&1 || die "curl absent (apt-get install -y curl)"
if [ "$DRY" = 0 ]; then
if ! command -v docker >/dev/null 2>&1; then
resoudre_docker "Docker n'est pas présent sur cette machine."
elif ! docker compose version >/dev/null 2>&1; then
resoudre_docker "Le greffon 'docker compose' (v2) est absent."
fi
docker info >/dev/null 2>&1 || die "docker injoignable (droits ? lance avec sudo)"
ok "docker + compose v2"
else
ok "prérequis non vérifiés (dry-run)"
fi
# --------------------------------------------------------- 2. répertoire + compose
log "Répertoire $INSTALL_DIR"
if [ "$DRY" = 0 ]; then
mkdir -p "$INSTALL_DIR"/{config,data,log}
tmp="$(mktemp)"
curl -fsSL "$BASE_URL/docker-compose.yml" -o "$tmp" || die "compose introuvable sur $BASE_URL"
grep -q 'streambox-aio' "$tmp" || die "compose téléchargé invalide"
if [ -f "$COMPOSE" ] && ! cmp -s "$tmp" "$COMPOSE"; then
cp "$COMPOSE" "$COMPOSE.bak.$(date +%Y%m%d%H%M%S)"
warn "compose précédent sauvegardé (.bak)"
fi
mv "$tmp" "$COMPOSE"
ok "compose à jour"
else
printf ' [dry-run] mkdir -p %s/{config,data,log}\n' "$INSTALL_DIR"
printf ' [dry-run] curl %s/docker-compose.yml -> %s\n' "$BASE_URL" "$COMPOSE"
fi
# ------------------------------------------------------------------- 3. l'image
log "Image $FULL_IMAGE"
have_local() {
[ "$DRY" = 1 ] && return 1
docker image inspect "$FULL_IMAGE" >/dev/null 2>&1
}
if [ -n "$SRC_TAR" ]; then
tar_path="$SRC_TAR"
if printf '%s' "$SRC_TAR" | grep -qE '^https?://'; then
tar_path="/tmp/$(basename "${SRC_TAR%%\?*}")"
log "Téléchargement de l'archive"
[ "$DRY" = 1 ] && printf ' [dry-run] curl -fL %s -o %s\n' "$SRC_TAR" "$tar_path" \
|| curl -fL --retry 2 "$SRC_TAR" -o "$tar_path"
fi
[ "$DRY" = 1 ] && printf ' [dry-run] docker load -i %s\n' "$tar_path" \
|| { docker load -i "$tar_path" || die "docker load a échoué"; }
ok "image chargée depuis $tar_path"
elif [ -n "$SRC_SSH" ]; then
log "Transfert depuis $SRC_SSH (docker save | docker load)"
if [ "$DRY" = 1 ]; then
printf ' [dry-run] ssh %s "sudo docker save %s" | docker load\n' "$SRC_SSH" "$FULL_IMAGE"
else
ssh "$SRC_SSH" "sudo docker save $FULL_IMAGE" | docker load \
|| die "transfert depuis $SRC_SSH échoué (image absente là-bas, ou clé SSH ?)"
ok "image transférée"
fi
elif [ -n "$SRC_REF" ]; then
[ "$DRY" = 1 ] && printf ' [dry-run] docker pull %s\n' "$SRC_REF" \
|| { docker pull "$SRC_REF" || die "pull de $SRC_REF échoué"; }
SRC_REF_ID="$(docker image inspect -f '{{.Id}}' "$SRC_REF" 2>/dev/null || true)"
if [ "$DRY" = 0 ] && [ -n "$SRC_REF_ID" ]; then
docker tag "$SRC_REF" "$FULL_IMAGE"
fi
ok "image tirée"
elif have_local; then
ok "image locale déjà présente"
elif [ "$DRY" = 1 ]; then
printf ' [dry-run] (image locale %s supposée présente)\n' "$FULL_IMAGE"
else
die "aucune image $FULL_IMAGE en local. Utilise --tar, --from-ssh, ou --image REPO:TAG"
fi
# ------------------------------------ 3bis. adoption d'un conteneur hors compose
# Un conteneur créé par `docker run` bloquera `up -d` (nom déjà pris). On l'arrête
# et on le renomme au lieu de le supprimer : il reste le rollback immédiat.
COMPOSE_PROJECT="${COMPOSE_PROJECT:-$(basename "$INSTALL_DIR")}"
BACKUP_NAME=""
if docker inspect streambox-aio >/dev/null 2>&1; then
proj="$(docker inspect -f '{{index .Config.Labels "com.docker.compose.project"}}' streambox-aio 2>/dev/null || true)"
if [ "$proj" != "$COMPOSE_PROJECT" ]; then
BACKUP_NAME="streambox-aio.avant-compose.$(date +%Y%m%d%H%M%S)"
log "Conteneur existant hors compose (projet='${proj:-aucun}')"
if [ "$DRY" = 1 ]; then
printf ' [dry-run] docker stop streambox-aio && docker rename streambox-aio %s\n' "$BACKUP_NAME"
else
docker stop streambox-aio >/dev/null
docker rename streambox-aio "$BACKUP_NAME"
ok "ancien conteneur arrêté et conservé sous $BACKUP_NAME"
fi
fi
fi
# ----------------------------------------------------------------- 4. démarrage
log "Démarrage"
if [ "$DRY" = 1 ]; then
printf ' [dry-run] cd %s && IMAGE_TAG=%s docker compose up -d\n' "$INSTALL_DIR" "$IMAGE_TAG"
else
( cd "$INSTALL_DIR" && IMAGE_TAG="$IMAGE_TAG" docker compose up -d ) || die "docker compose up a échoué"
fi
# --------------------------------------------------------------- 5. health check
log "Health check (max ${WAIT}s)"
if [ "$DRY" = 1 ]; then
printf ' [dry-run] curl http://127.0.0.1:%s/ jusqu à 200\n' "$UI_PORT"
exit 0
fi
state=""
for _ in $(seq 1 "$WAIT"); do
state="$(docker inspect -f '{{.State.Status}}' streambox-aio 2>/dev/null || echo absente)"
if [ "$state" = "running" ] && curl -fsS -o /dev/null -m 2 "http://127.0.0.1:$UI_PORT/" 2>/dev/null; then
echo
ok "conteneur running, HTTP 200 sur le port $UI_PORT"
printf '\n État : %s\n' "$state"
printf ' Image : %s\n' "$FULL_IMAGE"
printf ' Dossier: %s\n' "$INSTALL_DIR"
printf ' UI : http://%s:%s\n' "$(hostname -I 2>/dev/null | awk '{print $1}')" "$UI_PORT"
printf ' Logs : cd %s && docker compose logs -f\n' "$INSTALL_DIR"
printf ' Rollback: IMAGE_TAG=<tag précédent> docker compose up -d\n'
if [ -n "$BACKUP_NAME" ]; then
printf ' Retour au conteneur manuel :\n'
printf ' docker rm -f streambox-aio && docker rename %s streambox-aio && docker start streambox-aio\n' "$BACKUP_NAME"
fi
exit 0
fi
sleep 1
done
echo
warn "conteneur '$state' mais pas de 200 sur :$UI_PORT après ${WAIT}s"
docker inspect -f '{{.State.Status}} {{if .State.Health}}{{.State.Health.Status}}{{end}}' streambox-aio 2>/dev/null || true
die "démarrage incomplet — voir: cd $INSTALL_DIR && docker compose logs --tail 50"