From 28d07420912bf98287304a582563cf9b1844d73a Mon Sep 17 00:00:00 2001 From: Hermes Date: Thu, 5 Nov 2026 15:59:12 +0100 Subject: [PATCH] Installeur streambox : one-liner sans registry (tar / ssh save-load) --- README.md | 82 ++++++++++++++++++++- docker-compose.yml | 52 +++++++++++++ install.sh | 180 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 313 insertions(+), 1 deletion(-) create mode 100644 docker-compose.yml create mode 100755 install.sh diff --git a/README.md b/README.md index 7d43af3..cd0120d 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,83 @@ # streambox-install -One-liner d installation streambox (public, sans secret) \ No newline at end of file +One-liner d'installation / mise à jour de **streambox AIO**. Dépôt **public** : +il ne contient que le script et le `docker-compose.yml`, aucun secret (les +configs vivent dans `config/` sur la machine cible). Le code source reste dans +`hamriou/StreamBox`, privé. + +## Installation + +```bash +curl -fsSL https://git.les2ter.fr/gitea/hamriou/streambox-install/raw/branch/main/install.sh | sudo bash +``` + +Sans registry : l'image doit déjà être présente sur la machine (`docker load`). + +### Depuis une archive + +```bash +curl -fsSL .../install.sh | sudo bash -s -- --tar /tmp/streambox-aio-v49.tar --tag v49 +``` + +`--tar` accepte aussi une URL. + +### Depuis une autre machine du LAN + +```bash +curl -fsSL .../install.sh | sudo bash -s -- --from-ssh lab --tag v49 +``` + +Équivalent à `ssh lab 'sudo docker save streambox-aio:v49' | docker load`, puis +`up -d`. Aucun port entrant à ouvrir, aucune authentification registry. + +### Depuis un registry (option) + +```bash +curl -fsSL .../install.sh | sudo bash -s -- --image git.les2ter.fr/hamriou/streambox-aio:v49 +``` + +## Options + +| Option | Rôle | +|---|---| +| `--dir DIR` | répertoire d'installation (défaut `~/aio`) | +| `--tag TAG` | tag de l'image (défaut `latest`) | +| `--tar FICHIER\|URL` | charger l'image par `docker load` | +| `--from-ssh HOST` | `ssh HOST 'docker save …' \| docker load` | +| `--image REPO:TAG` | `docker pull` puis tag local | +| `--wait N` | attente max du health check (défaut 90 s) | +| `--dry-run` | affiche les actions sans rien modifier | + +## Mise à jour + +```bash +# sur la machine source +docker save streambox-aio:v50 | ssh vps 'sudo docker load' +# sur le VPS +curl -fsSL .../install.sh | sudo bash -s -- --tag v50 +``` + +L'installeur sauvegarde le `docker-compose.yml` précédent en `.bak.` s'il +change, et ne touche jamais `config/`, `data/` ni `log/`. + +## Rollback + +```bash +cd ~/aio && IMAGE_TAG=v49 docker compose up -d +``` + +L'image précédente doit être encore présente (`docker image ls streambox-aio`). + +## Vérification + +Après démarrage : `running` + HTTP 200 sur `http://:3005/`. En cas d'échec +l'installeur sort en erreur avec la commande de logs à lancer. + +## Pourquoi pas le registry Gitea + +Le registry de cette instance (`git.les2ter.fr`) refuse les jetons +authentifiés : `/v2/token` renvoie un JWT `UserID: -1` avec un `Scope` vide, +car `ENABLE_BASIC_AUTHENTICATION = false` (instance YunoHost en SSO). Docker +n'obtient un token registry que par HTTP Basic → « Login Succeeded » puis +`unauthorized`. `docker save | docker load` contourne le problème sans toucher +à `app.ini` ni affaiblir le SSO. diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..60be7c2 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,52 @@ +# streambox AIO — compose d'installation (dépôt public) +# +# L'image est chargée localement (docker load / docker save | docker load), +# jamais tirée d'un registry : d'où `pull_policy: never`. +# +# IMAGE_TAG=v49 docker compose up -d +# Rollback : IMAGE_TAG=v48 docker compose up -d (config/ et data/ intacts) +# +# Mêmes ports, volumes et politique de restart que la production VPS. + +services: + streambox-aio: + image: streambox-aio:${IMAGE_TAG:-latest} + pull_policy: never + container_name: streambox-aio + restart: unless-stopped + network_mode: bridge + shm_size: 64m + ports: + - "13000:3000" + - "3005:3005" + - "5055:5055" + - "5690:5690" + - "6500:6500" + - "7878:7878" + - "7879:7879" + - "8096:8096" + - "8191:8191" + - "8989:8989" + - "8990:8990" + - "9696:9696" + volumes: + - ./config:/config + - ./data:/data + - ./log:/log + environment: + TZ: Europe/Paris + AIO_LOG_DIR: /log + AIO_UI_PORT: "3005" + DEBIAN_FRONTEND: noninteractive + HOME: /config + DOTNET_RUNNING_IN_CONTAINER: "true" + DOTNET_CLI_TELEMETRY_OPTOUT: "1" + LANG: fr_FR.UTF-8 + LC_ALL: fr_FR.UTF-8 + CHROME_BIN: /usr/bin/chromium + CHROMEDRIVER_PATH: /usr/bin/chromedriver + logging: + driver: json-file + options: + max-size: "20m" + max-file: "3" diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..49836f3 --- /dev/null +++ b/install.sh @@ -0,0 +1,180 @@ +#!/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.les2ter.fr/gitea/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.les2ter.fr/gitea/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}" + +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) + --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: 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 ;; + --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" +command -v docker >/dev/null || die "docker absent" +command -v curl >/dev/null || die "curl absent" +if [ "$DRY" = 0 ]; then + docker info >/dev/null 2>&1 || die "docker injoignable (droits ? lance avec sudo)" + docker compose version >/dev/null 2>&1 || die "docker compose v2 absent" +fi +ok "docker + compose v2" + +# --------------------------------------------------------- 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 + +# ----------------------------------------------------------------- 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= docker compose up -d\n' + 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"