From fd92876381732c148d8e53eb9b97d887f5fc22e4 Mon Sep 17 00:00:00 2001 From: GuiPoM <11942518+GuiPoM@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:55:35 +0200 Subject: [PATCH] Initial commit --- .gitattributes | 2 + README.md | 127 +++++++++++++++++++++++++ nas-docker-prune | 22 +++++ nas-docker-pull | 90 ++++++++++++++++++ nas-docker-up | 224 +++++++++++++++++++++++++++++++++++++++++++++ nas-ha-config.yaml | 51 +++++++++++ nas-update | 202 ++++++++++++++++++++++++++++++++++++++++ nas-update-system | 80 ++++++++++++++++ nas-upgrade-system | 81 ++++++++++++++++ 9 files changed, 879 insertions(+) create mode 100644 .gitattributes create mode 100644 README.md create mode 100644 nas-docker-prune create mode 100644 nas-docker-pull create mode 100644 nas-docker-up create mode 100644 nas-ha-config.yaml create mode 100644 nas-update create mode 100644 nas-update-system create mode 100644 nas-upgrade-system diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7227844 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +* text=auto +nas-* text eol=lf diff --git a/README.md b/README.md new file mode 100644 index 0000000..f86adee --- /dev/null +++ b/README.md @@ -0,0 +1,127 @@ +# nas-scripts + +Scripts de gestion des mises à jour pour NAS sous OpenMediaVault 8. + +## Installation + +```bash +cp nas-update-system nas-upgrade-system nas-docker-pull nas-docker-up nas-docker-prune nas-update /usr/local/bin/ +chmod +x /usr/local/bin/nas-update-system /usr/local/bin/nas-upgrade-system \ + /usr/local/bin/nas-docker-pull /usr/local/bin/nas-docker-up \ + /usr/local/bin/nas-docker-prune /usr/local/bin/nas-update +``` + +## Scripts + +### `nas-update` + +Script racine interactif. Orchestre toutes les étapes dans l'ordre : + +1. Analyse système (apt) +2. Pull et détection des mises à jour Docker +3. Upgrade système (confirmation) +4. Upgrade Docker (tout ou conteneur par conteneur) +5. Nettoyage des images orphelines + +```bash +nas-update +``` + +--- + +### `nas-update-system` + +Vérifie les mises à jour système disponibles via apt. Ne modifie rien. + +- Mode terminal : affichage coloré des paquets upgradables +- Mode non-interactif (HA) : output JSON + +```bash +nas-update-system +``` + +```json +{"count":2,"reboot_required":false,"packages":[{"name":"curl","current":"7.88.0","available":"7.88.1"}]} +``` + +--- + +### `nas-upgrade-system` + +Applique les mises à jour système (`apt full-upgrade`). + +- Mode terminal : affiche le bilan + confirmation avant d'appliquer +- Mode non-interactif (HA) : applique directement + +```bash +nas-upgrade-system +``` + +--- + +### `nas-docker-pull` + +Pull toutes les images Docker des conteneurs actifs et détecte les mises à jour disponibles. **Ne recrée pas les conteneurs.** + +Idempotent : tant que `nas-docker-up` n'a pas recréé les conteneurs, le check détecte toujours l'écart. + +- Mode terminal : affichage coloré +- Mode non-interactif (HA) : output JSON + +```bash +nas-docker-pull +``` + +```json +{"count":1,"containers":[{"name":"jellyfin","image":"jellyfin/jellyfin:latest","compose_dir":"/opt/stacks/jellyfin","current":"10.9.0","available":"disponible"}]} +``` + +--- + +### `nas-docker-up` + +Recrée les conteneurs sur la nouvelle image via `docker compose up -d --remove-orphans`. + +- Sans argument : propose la mise à jour de tous les conteneurs ayant une image plus récente +- Avec argument : cible une stack spécifique +- Mode terminal : confirmation par stack ou tout d'un coup +- Mode non-interactif (HA) : applique directement + +```bash +nas-docker-up # toutes les stacks +nas-docker-up jellyfin # stack spécifique +``` + +--- + +### `nas-docker-prune` + +Nettoie les images Docker orphelines (dangling). À appeler après `nas-docker-up`. + +```bash +nas-docker-prune +``` + +--- + +## Intégration Home Assistant + +Voir `nas-ha-config.yaml` pour la configuration complète. + +```yaml +shell_command: + nas_update_system: "ssh -F /config/.ssh/config omv 'sudo nas-update-system'" + nas_upgrade_system: "ssh -F /config/.ssh/config omv 'sudo nas-upgrade-system'" + nas_docker_pull: "ssh -F /config/.ssh/config omv 'sudo nas-docker-pull'" + nas_docker_up: "ssh -F /config/.ssh/config omv 'sudo nas-docker-up'" + nas_docker_up_stack: "ssh -F /config/.ssh/config omv 'sudo nas-docker-up {{ stack }}'" + nas_docker_prune: "ssh -F /config/.ssh/config omv 'sudo nas-docker-prune'" +``` + +## Flux typique depuis HA + +``` +nas_docker_pull → détecte les mises à jour dispo (idempotent) +nas_docker_up → applique les mises à jour +nas_docker_prune → nettoie les anciennes images +``` diff --git a/nas-docker-prune b/nas-docker-prune new file mode 100644 index 0000000..da17504 --- /dev/null +++ b/nas-docker-prune @@ -0,0 +1,22 @@ +#!/bin/bash +# nas-docker-prune — Nettoie les images Docker orphelines (dangling) +# Usage : nas-docker-prune +# À appeler après nas-docker-up pour supprimer les anciennes images remplacées + +set -euo pipefail + +if [ -t 1 ]; then INTERACTIVE=true; else INTERACTIVE=false; fi + +if $INTERACTIVE; then + CYAN='\033[0;36m' + GREEN='\033[0;32m' + BOLD='\033[1m' + RESET='\033[0m' + echo -e "${CYAN}🧹 Nettoyage des images orphelines...${RESET}" +fi + +docker image prune -f + +if $INTERACTIVE; then + echo -e "${GREEN}✅ Nettoyage terminé.${RESET}" +fi diff --git a/nas-docker-pull b/nas-docker-pull new file mode 100644 index 0000000..2a6f33c --- /dev/null +++ b/nas-docker-pull @@ -0,0 +1,90 @@ +#!/bin/bash +# nas-docker-pull — Pull les images Docker et détecte les mises à jour disponibles +# Usage : nas-docker-pull +# Output : JSON (mode non-interactif) ou texte coloré (mode terminal) +# Idempotent : tant que nas-docker-up n'a pas recréé les conteneurs, +# le conteneur tourne sur l'ancien image ID → l'écart est toujours détecté + +set -euo pipefail + +if [ -t 1 ]; then INTERACTIVE=true; else INTERACTIVE=false; fi + +# Couleurs (mode terminal uniquement) +if $INTERACTIVE; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + CYAN='\033[0;36m' + BOLD='\033[1m' + RESET='\033[0m' +else + RED='' GREEN='' YELLOW='' CYAN='' BOLD='' RESET='' +fi + +containers_json="" +count=0 + +if $INTERACTIVE; then + echo -e "${BOLD}--- Vérification des images Docker ---${RESET}" +fi + +while IFS=: read -r container_id container_name; do + # Récupérer le compose_dir via le label + compose_dir=$(docker inspect --format='{{index .Config.Labels "com.docker.compose.project.working_dir"}}' "$container_id" 2>/dev/null | xargs) + + # Ignorer les conteneurs hors compose + if [ -z "$compose_dir" ] || [ ! -d "$compose_dir" ]; then + continue + fi + + image_name=$(docker inspect --format='{{.Config.Image}}' "$container_id") + old_image_id=$(docker inspect --format='{{.Image}}' "$container_id") + old_ver=$(docker inspect --format='{{index .Config.Labels "org.opencontainers.image.version"}}' "$container_id" 2>/dev/null || echo "") + [ -z "$old_ver" ] && old_ver="inconnue" + + if $INTERACTIVE; then + echo -ne " Vérification de ${CYAN}${container_name}${RESET}... " + fi + + if ! docker pull "$image_name" > /dev/null 2>&1; then + if $INTERACTIVE; then echo -e "${YELLOW}⚠ Erreur de pull (ignoré)${RESET}"; fi + continue + fi + + new_image_id=$(docker inspect --format='{{.Id}}' "$image_name" 2>/dev/null) + + if [ "$old_image_id" != "$new_image_id" ]; then + new_ver=$(docker inspect --format='{{index .Config.Labels "org.opencontainers.image.version"}}' "$image_name" 2>/dev/null || echo "") + [ -z "$new_ver" ] && new_ver="disponible" + + if $INTERACTIVE; then + echo -e "${YELLOW}MAJ DISPONIBLE${RESET} : ${YELLOW}${old_ver}${RESET} → ${GREEN}${new_ver}${RESET}" + fi + + entry="{\"name\":\"${container_name}\",\"image\":\"${image_name}\",\"compose_dir\":\"${compose_dir}\",\"current\":\"${old_ver}\",\"available\":\"${new_ver}\"}" + if [ $count -eq 0 ]; then + containers_json="${entry}" + else + containers_json="${containers_json},${entry}" + fi + count=$((count + 1)) + else + if $INTERACTIVE; then echo -e "${GREEN}✅ À jour${RESET}"; fi + fi + +done < <(docker ps --format "{{.ID}}:{{.Names}}") + +# Toujours écrire le JSON dans /tmp pour nas-update +printf '{"count":%d,"containers":[%s]}\n' "$count" "$containers_json" > /tmp/nas-docker-pull.json + +if $INTERACTIVE; then + echo "" + echo -e "${BOLD}--- Bilan ---${RESET}" + if [ $count -eq 0 ]; then + echo -e "${GREEN}✅ Tous les conteneurs sont à jour.${RESET}" + else + echo -e "${YELLOW}🐳 ${count} conteneur(s) à mettre à jour.${RESET}" + fi +else + cat /tmp/nas-docker-pull.json +fi diff --git a/nas-docker-up b/nas-docker-up new file mode 100644 index 0000000..df094f8 --- /dev/null +++ b/nas-docker-up @@ -0,0 +1,224 @@ +#!/bin/bash +# nas-docker-up — Applique les mises à jour Docker via docker compose up -d +# Usage : nas-docker-up [stack_name] +# Sans argument : met à jour tous les conteneurs ayant une image plus récente +# Avec argument : met à jour uniquement la stack spécifiée +# Mode HA (non-interactif) : applique directement, pas de prompt +# Mode terminal : confirmation par stack (sauf si stack spécifiée en argument) + +set -euo pipefail + +if [ -t 1 ]; then INTERACTIVE=true; else INTERACTIVE=false; fi + +TARGET_STACK="${1:-}" + +# Couleurs (mode terminal uniquement) +if $INTERACTIVE; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + CYAN='\033[0;36m' + BOLD='\033[1m' + RESET='\033[0m' +else + RED='' GREEN='' YELLOW='' CYAN='' BOLD='' RESET='' +fi + +# --- Détection de l'env global OMV --- +OMV_GLOBAL_ENV="" +if [ -f "/etc/omv-compose.env" ]; then + OMV_GLOBAL_ENV="/etc/omv-compose.env" +elif [ -f "/srv/omv-compose.env" ]; then + OMV_GLOBAL_ENV="/srv/omv-compose.env" +fi + +# --- Collecte des conteneurs à mettre à jour --- +containers_to_update=() +images_to_update=() +compose_dirs=() +container_ids=() +old_versions=() +new_versions=() + +if $INTERACTIVE; then + echo -e "${BOLD}--- Phase 1 : Recherche des mises à jour ---${RESET}" +fi + +while IFS=: read -r container_id container_name; do + # Filtrer par stack si argument fourni + if [ -n "$TARGET_STACK" ] && [ "$container_name" != "$TARGET_STACK" ]; then + stack_label=$(docker inspect --format='{{index .Config.Labels "com.docker.compose.project"}}' "$container_id" 2>/dev/null | xargs) + if [ "$stack_label" != "$TARGET_STACK" ]; then + continue + fi + fi + + compose_dir=$(docker inspect --format='{{index .Config.Labels "com.docker.compose.project.working_dir"}}' "$container_id" 2>/dev/null | xargs) + + if [ -z "$compose_dir" ] || [ ! -d "$compose_dir" ]; then + continue + fi + + image_name=$(docker inspect --format='{{.Config.Image}}' "$container_id") + old_image_id=$(docker inspect --format='{{.Image}}' "$container_id") + old_ver=$(docker inspect --format='{{index .Config.Labels "org.opencontainers.image.version"}}' "$container_id" 2>/dev/null || echo "") + [ -z "$old_ver" ] && old_ver="inconnue" + + if $INTERACTIVE; then + echo -ne " Vérification de ${CYAN}${container_name}${RESET}... " + fi + + if ! docker pull "$image_name" > /dev/null 2>&1; then + if $INTERACTIVE; then echo -e "${YELLOW}⚠ Erreur de pull (ignoré)${RESET}"; fi + continue + fi + + new_image_id=$(docker inspect --format='{{.Id}}' "$image_name" 2>/dev/null) + + if [ "$old_image_id" != "$new_image_id" ]; then + new_ver=$(docker inspect --format='{{index .Config.Labels "org.opencontainers.image.version"}}' "$image_name" 2>/dev/null || echo "") + [ -z "$new_ver" ] && new_ver="disponible" + + if $INTERACTIVE; then + echo -e "${YELLOW}MAJ DISPONIBLE${RESET} : ${YELLOW}${old_ver}${RESET} → ${GREEN}${new_ver}${RESET}" + fi + + containers_to_update+=("$container_name") + images_to_update+=("$image_name") + compose_dirs+=("$compose_dir") + container_ids+=("$container_id") + old_versions+=("$old_ver") + new_versions+=("$new_ver") + else + if $INTERACTIVE; then echo -e "${GREEN}✅ À jour${RESET}"; fi + fi + +done < <(docker ps --format "{{.ID}}:{{.Names}}") + +if [ ${#containers_to_update[@]} -eq 0 ]; then + if $INTERACTIVE; then + echo "" + echo -e "${GREEN}✅ Aucun conteneur à mettre à jour.${RESET}" + echo -e "${CYAN}🧹 Nettoyage des images orphelines...${RESET}" + fi + docker image prune -f > /dev/null + exit 0 +fi + +if $INTERACTIVE; then + echo "" + echo -e "${BOLD}--- Phase 2 : Bilan des mises à jour disponibles ---${RESET}" + for i in "${!containers_to_update[@]}"; do + echo -e " • ${CYAN}${containers_to_update[$i]}${RESET} : ${YELLOW}${old_versions[$i]}${RESET} → ${GREEN}${new_versions[$i]}${RESET}" + done + echo "" + + # Si stack spécifique : pas de question sur le mode, on applique directement + if [ -z "$TARGET_STACK" ]; then + UPDATE_ALL=false + while true; do + read -p "Mettre à jour TOUS ces conteneurs ? [y]es (Tout) / [n]o (Choisir par conteneur) : " global_choice + case "$global_choice" in + [yY]*) UPDATE_ALL=true; break ;; + [nN]*) UPDATE_ALL=false; break ;; + *) echo "Veuillez répondre par y ou n." ;; + esac + done + else + UPDATE_ALL=true + fi +fi + +# --- Phase 3 : Application --- +if $INTERACTIVE; then + echo "" + echo -e "${BOLD}--- Phase 3 : Application des mises à jour ---${RESET}" +fi + +START_DIR=$(pwd) +upgraded=0 +failed=0 + +for i in "${!containers_to_update[@]}"; do + c_name="${containers_to_update[$i]}" + c_dir="${compose_dirs[$i]}" + c_old_ver="${old_versions[$i]}" + c_new_ver="${new_versions[$i]}" + + DO_UPDATE=false + + if ! $INTERACTIVE; then + # Mode HA : toujours appliquer + DO_UPDATE=true + elif [ "$UPDATE_ALL" = true ]; then + DO_UPDATE=true + else + while true; do + read -p "Mettre à jour '${c_name}' (${c_old_ver} → ${c_new_ver}) ? [y/n] : " choice + case "$choice" in + [yY]*) DO_UPDATE=true; break ;; + [nN]*) DO_UPDATE=false; break ;; + *) echo "Veuillez répondre par y ou n." ;; + esac + done + fi + + if [ "$DO_UPDATE" = true ]; then + if $INTERACTIVE; then + echo -e " 🚀 Mise à jour de ${CYAN}${c_name}${RESET} dans ${c_dir}..." + fi + + if cd "$c_dir" 2>/dev/null; then + ENV_ARGS="" + + # Env global OMV + if [ -n "$OMV_GLOBAL_ENV" ] && [ -f "$OMV_GLOBAL_ENV" ]; then + ENV_ARGS="--env-file $OMV_GLOBAL_ENV" + else + # Fallback : chercher global.env ou .env dans le dossier parent + PARENT_DIR=$(dirname "$c_dir") + if [ -f "$PARENT_DIR/global.env" ]; then + ENV_ARGS="--env-file $PARENT_DIR/global.env" + elif [ -f "$PARENT_DIR/.env" ]; then + ENV_ARGS="--env-file $PARENT_DIR/.env" + fi + fi + + # .env local (toujours ajouté s'il existe) + if [ -f ".env" ]; then + ENV_ARGS="$ENV_ARGS --env-file .env" + fi + + if docker compose $ENV_ARGS up -d --remove-orphans 2>&1; then + if $INTERACTIVE; then + echo -e " ${GREEN}✅ ${c_name} mis à jour avec succès.${RESET}" + fi + upgraded=$((upgraded + 1)) + else + if $INTERACTIVE; then + echo -e " ${RED}❌ Échec de la mise à jour pour ${c_name}.${RESET}" + fi + failed=$((failed + 1)) + fi + else + if $INTERACTIVE; then + echo -e " ${RED}❌ Impossible d'accéder à ${c_dir}.${RESET}" + fi + failed=$((failed + 1)) + fi + else + if $INTERACTIVE; then + echo -e " ⏭ Mise à jour ignorée pour ${c_name}." + fi + fi + + cd "$START_DIR" +done + +if $INTERACTIVE; then + echo "" + echo -e "${BOLD}--- Terminé ---${RESET}" + echo -e " ${GREEN}✅ ${upgraded} mis à jour${RESET} ${RED}❌ ${failed} en échec${RESET}" +else + printf '{"upgraded":%d,"failed":%d}\n' "$upgraded" "$failed" +fi diff --git a/nas-ha-config.yaml b/nas-ha-config.yaml new file mode 100644 index 0000000..ee8d357 --- /dev/null +++ b/nas-ha-config.yaml @@ -0,0 +1,51 @@ +# configuration.yaml — Home Assistant shell_command pour le NAS +# +# Ajouter dans configuration.yaml : +# +# shell_command: +# nas_update_system: "ssh -F /config/.ssh/config omv 'sudo nas-update-system'" +# nas_upgrade_system: "ssh -F /config/.ssh/config omv 'sudo nas-upgrade-system'" +# nas_docker_pull: "ssh -F /config/.ssh/config omv 'sudo nas-docker-pull'" +# nas_docker_up: "ssh -F /config/.ssh/config omv 'sudo nas-docker-up'" +# nas_docker_up_stack: "ssh -F /config/.ssh/config omv 'sudo nas-docker-up {{ stack }}'" +# +# Exemples d'utilisation depuis un script HA ou une automation : +# +# service: shell_command.nas_update_system +# service: shell_command.nas_upgrade_system +# service: shell_command.nas_docker_pull +# service: shell_command.nas_docker_up +# service: shell_command.nas_docker_up_stack +# data: +# stack: jellyfin +# +# ───────────────────────────────────────────────────────────────────────────── +# Sensor command_line pour exposer le résultat du check système dans HA +# ───────────────────────────────────────────────────────────────────────────── +# +# command_line: +# - sensor: +# name: NAS System Updates +# unique_id: nas_system_updates +# command: "ssh -F /config/.ssh/config omv 'sudo nas-update-system'" +# scan_interval: 3600 +# value_template: "{{ value_json.count }}" +# json_attributes: +# - packages +# - reboot_required +# +# - sensor: +# name: NAS Docker Updates +# unique_id: nas_docker_updates +# command: "ssh -F /config/.ssh/config omv 'sudo nas-docker-pull'" +# scan_interval: 3600 +# value_template: "{{ value_json.count }}" +# json_attributes: +# - containers +# +# Ces sensors exposent : +# - sensor.nas_system_updates → nombre de paquets apt upgradables +# - sensor.nas_docker_updates → nombre de conteneurs Docker à mettre à jour +# - sensor.nas_system_updates.reboot_required → true/false +# - sensor.nas_system_updates.packages → liste des paquets +# - sensor.nas_docker_updates.containers → liste des conteneurs diff --git a/nas-update b/nas-update new file mode 100644 index 0000000..9cb7a7b --- /dev/null +++ b/nas-update @@ -0,0 +1,202 @@ +#!/bin/bash +# nas-update — Script racine de gestion des mises à jour NAS +# Orchestre nas-update-system, nas-upgrade-system, nas-docker-pull, nas-docker-up, nas-docker-prune +# Usage : nas-update + +set -euo pipefail + +# Couleurs +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +CYAN='\033[0;36m' +BOLD='\033[1m' +RESET='\033[0m' + +echo -e "${BOLD}=====================================================${RESET}" +echo -e "${BOLD} NAS — Gestionnaire de mises à jour ${RESET}" +echo -e "${BOLD}=====================================================${RESET}" +echo "" + +# ───────────────────────────────────────────────────── +# Phase 1 : Analyse système +# ───────────────────────────────────────────────────── +echo -e "${BOLD}--- Phase 1 : Analyse système (apt) ---${RESET}" +nas-update-system +echo "" + +# ───────────────────────────────────────────────────── +# Phase 2 : Analyse Docker (pull + détection diffs) +# ───────────────────────────────────────────────────── +echo -e "${BOLD}--- Phase 2 : Analyse Docker ---${RESET}" + +nas-docker-pull + +# Lire le JSON écrit par nas-docker-pull +docker_json=$(cat /tmp/nas-docker-pull.json 2>/dev/null || echo '{"count":0,"containers":[]}') + +containers_to_update=() +compose_dirs=() +old_versions=() +new_versions=() + +while IFS= read -r line; do + name=$(echo "$line" | grep -o '"name":"[^"]*"' | cut -d'"' -f4) + compose_dir=$(echo "$line" | grep -o '"compose_dir":"[^"]*"' | cut -d'"' -f4) + current=$(echo "$line" | grep -o '"current":"[^"]*"' | cut -d'"' -f4) + available=$(echo "$line" | grep -o '"available":"[^"]*"' | cut -d'"' -f4) + [ -z "$name" ] && continue + containers_to_update+=("$name") + compose_dirs+=("$compose_dir") + old_versions+=("$current") + new_versions+=("$available") +done < <(echo "$docker_json" | grep -o '{[^}]*}') + +echo "" + +# ───────────────────────────────────────────────────── +# Phase 3 : Upgrade système +# ───────────────────────────────────────────────────── +echo -e "${BOLD}--- Phase 3 : Mise à jour système ---${RESET}" + +while true; do + read -p "Appliquer apt full-upgrade ? [y/n] : " sys_choice + case "$sys_choice" in + [yY]*) + nas-upgrade-system + break + ;; + [nN]*) + echo -e "${YELLOW}⏭ Mise à jour système ignorée.${RESET}" + break + ;; + *) echo "Veuillez répondre par y ou n." ;; + esac +done + +echo "" + +# ───────────────────────────────────────────────────── +# Phase 4 : Upgrade Docker +# ───────────────────────────────────────────────────── +echo -e "${BOLD}--- Phase 4 : Mise à jour Docker ---${RESET}" + +if [ ${#containers_to_update[@]} -eq 0 ]; then + echo -e "${GREEN}✅ Aucun conteneur à mettre à jour.${RESET}" + echo "" + echo -e "${BOLD}=====================================================${RESET}" + echo -e "${GREEN} Terminé ! ${RESET}" + echo -e "${BOLD}=====================================================${RESET}" + exit 0 +fi + +echo "" +echo -e "${BOLD}Bilan des conteneurs à mettre à jour :${RESET}" +for i in "${!containers_to_update[@]}"; do + echo -e " • ${CYAN}${containers_to_update[$i]}${RESET} : ${YELLOW}${old_versions[$i]}${RESET} → ${GREEN}${new_versions[$i]}${RESET}" +done +echo "" + +UPDATE_ALL=false +while true; do + read -p "Mettre à jour TOUS ces conteneurs ? [y]es (Tout) / [n]o (Choisir par conteneur) / [s]kip (Ignorer tout) : " docker_choice + case "$docker_choice" in + [yY]*) UPDATE_ALL=true; break ;; + [nN]*) UPDATE_ALL=false; break ;; + [sS]*) echo -e "${YELLOW}⏭ Mise à jour Docker ignorée.${RESET}"; break ;; + *) echo "Veuillez répondre par y, n ou s." ;; + esac +done + +if [[ "$docker_choice" =~ ^[sS] ]]; then + echo "" + echo -e "${BOLD}=====================================================${RESET}" + echo -e "${GREEN} Terminé ! ${RESET}" + echo -e "${BOLD}=====================================================${RESET}" + exit 0 +fi + +# Détection de l'env global OMV +OMV_GLOBAL_ENV="" +if [ -f "/etc/omv-compose.env" ]; then + OMV_GLOBAL_ENV="/etc/omv-compose.env" +elif [ -f "/srv/omv-compose.env" ]; then + OMV_GLOBAL_ENV="/srv/omv-compose.env" +fi + +START_DIR=$(pwd) +upgraded=0 +failed=0 + +for i in "${!containers_to_update[@]}"; do + c_name="${containers_to_update[$i]}" + c_dir="${compose_dirs[$i]}" + c_old_ver="${old_versions[$i]}" + c_new_ver="${new_versions[$i]}" + + DO_UPDATE=false + + if [ "$UPDATE_ALL" = true ]; then + DO_UPDATE=true + else + while true; do + read -p " Mettre à jour '${c_name}' (${c_old_ver} → ${c_new_ver}) ? [y/n] : " choice + case "$choice" in + [yY]*) DO_UPDATE=true; break ;; + [nN]*) DO_UPDATE=false; break ;; + *) echo "Veuillez répondre par y ou n." ;; + esac + done + fi + + if [ "$DO_UPDATE" = true ]; then + echo -e " 🚀 Mise à jour de ${CYAN}${c_name}${RESET}..." + + if cd "$c_dir" 2>/dev/null; then + ENV_ARGS="" + + if [ -n "$OMV_GLOBAL_ENV" ] && [ -f "$OMV_GLOBAL_ENV" ]; then + ENV_ARGS="--env-file $OMV_GLOBAL_ENV" + else + PARENT_DIR=$(dirname "$c_dir") + if [ -f "$PARENT_DIR/global.env" ]; then + ENV_ARGS="--env-file $PARENT_DIR/global.env" + elif [ -f "$PARENT_DIR/.env" ]; then + ENV_ARGS="--env-file $PARENT_DIR/.env" + fi + fi + + if [ -f ".env" ]; then + ENV_ARGS="$ENV_ARGS --env-file .env" + fi + + if docker compose $ENV_ARGS up -d --remove-orphans 2>&1; then + echo -e " ${GREEN}✅ ${c_name} mis à jour avec succès.${RESET}" + upgraded=$((upgraded + 1)) + else + echo -e " ${RED}❌ Échec pour ${c_name}.${RESET}" + failed=$((failed + 1)) + fi + else + echo -e " ${RED}❌ Impossible d'accéder à ${c_dir}.${RESET}" + failed=$((failed + 1)) + fi + else + echo -e " ⏭ Ignoré : ${c_name}" + fi + + cd "$START_DIR" +done + +# ───────────────────────────────────────────────────── +# Phase 5 : Nettoyage des images orphelines +# ───────────────────────────────────────────────────── +echo "" +echo -e "${BOLD}--- Phase 5 : Nettoyage ---${RESET}" +nas-docker-prune + +echo "" +echo -e "${BOLD}=====================================================${RESET}" +echo -e "${BOLD} Terminé ! ${RESET}" +echo -e " Docker : ${GREEN}✅ ${upgraded} mis à jour${RESET} ${RED}❌ ${failed} en échec${RESET}" +echo -e "${BOLD}=====================================================${RESET}" diff --git a/nas-update-system b/nas-update-system new file mode 100644 index 0000000..b06790b --- /dev/null +++ b/nas-update-system @@ -0,0 +1,80 @@ +#!/bin/bash +# nas-update-system — Vérifie les mises à jour système disponibles (apt) +# Usage : nas-update-system +# Output : JSON (mode non-interactif) ou texte coloré (mode terminal) + +set -euo pipefail + +if [ -t 1 ]; then INTERACTIVE=true; else INTERACTIVE=false; fi + +# Couleurs (mode terminal uniquement) +if $INTERACTIVE; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + CYAN='\033[0;36m' + BOLD='\033[1m' + RESET='\033[0m' +else + RED='' GREEN='' YELLOW='' CYAN='' BOLD='' RESET='' +fi + +if $INTERACTIVE; then + echo -e "${BOLD}--- Mise à jour de la liste des paquets ---${RESET}" +fi + +apt-get update -qq 2>/dev/null + +# Parser apt list --upgradable +# Format : nom/suite version arch [upgradable from: ancienne_version] +packages_json="" +count=0 + +while IFS= read -r line; do + # Ignorer la ligne d'avertissement "Listing..." + [[ "$line" =~ ^Listing ]] && continue + [[ -z "$line" ]] && continue + + # Extraire le nom (avant le /) + name=$(echo "$line" | cut -d'/' -f1) + # Extraire la version disponible (2ème champ) + available=$(echo "$line" | awk '{print $2}') + # Extraire la version actuelle (après "upgradable from:") + current=$(echo "$line" | grep -oP 'upgradable from: \K[^\]]+' || echo "inconnue") + + if $INTERACTIVE; then + echo -e " ${CYAN}${name}${RESET} : ${YELLOW}${current}${RESET} → ${GREEN}${available}${RESET}" + fi + + # Construire JSON + entry="{\"name\":\"${name}\",\"current\":\"${current}\",\"available\":\"${available}\"}" + if [ $count -eq 0 ]; then + packages_json="${entry}" + else + packages_json="${packages_json},${entry}" + fi + count=$((count + 1)) + +done < <(apt list --upgradable 2>/dev/null) + +# Vérifier si reboot requis +reboot_required=false +if [ -f /var/run/reboot-required ]; then + reboot_required=true +fi + +if $INTERACTIVE; then + echo "" + echo -e "${BOLD}--- Bilan ---${RESET}" + if [ $count -eq 0 ]; then + echo -e "${GREEN}✅ Système à jour.${RESET}" + else + echo -e "${YELLOW}📦 ${count} paquet(s) à mettre à jour.${RESET}" + fi + if $reboot_required; then + echo -e "${RED}⚠️ Redémarrage requis.${RESET}" + fi +else + printf '{"count":%d,"reboot_required":%s,"packages":[%s]}\n' \ + "$count" "$reboot_required" "$packages_json" +fi diff --git a/nas-upgrade-system b/nas-upgrade-system new file mode 100644 index 0000000..38c08e1 --- /dev/null +++ b/nas-upgrade-system @@ -0,0 +1,81 @@ +#!/bin/bash +# nas-upgrade-system — Applique les mises à jour système (apt full-upgrade) +# Usage : nas-upgrade-system +# Mode HA (non-interactif) : applique directement, output JSON +# Mode terminal : affiche le bilan + confirmation avant d'appliquer + +set -euo pipefail + +if [ -t 1 ]; then INTERACTIVE=true; else INTERACTIVE=false; fi + +# Couleurs (mode terminal uniquement) +if $INTERACTIVE; then + RED='\033[0;31m' + GREEN='\033[0;32m' + YELLOW='\033[1;33m' + CYAN='\033[0;36m' + BOLD='\033[1m' + RESET='\033[0m' +else + RED='' GREEN='' YELLOW='' CYAN='' BOLD='' RESET='' +fi + +# S'assurer que la liste est à jour +if $INTERACTIVE; then + echo -e "${BOLD}--- Mise à jour de la liste des paquets ---${RESET}" +fi +apt-get update -qq 2>/dev/null + +# Récupérer les paquets upgradables +upgradable=$(apt list --upgradable 2>/dev/null | grep -v '^Listing' | grep -v '^$') +count=$(echo "$upgradable" | grep -c . || true) + +if [ "$count" -eq 0 ]; then + if $INTERACTIVE; then + echo -e "${GREEN}✅ Système déjà à jour, rien à faire.${RESET}" + else + printf '{"status":"already_up_to_date","upgraded":0,"reboot_required":false}\n' + fi + exit 0 +fi + +if $INTERACTIVE; then + echo "" + echo -e "${BOLD}--- Paquets à mettre à jour (${count}) ---${RESET}" + while IFS= read -r line; do + [[ -z "$line" ]] && continue + name=$(echo "$line" | cut -d'/' -f1) + available=$(echo "$line" | awk '{print $2}') + current=$(echo "$line" | grep -oP 'upgradable from: \K[^\]]+' || echo "inconnue") + echo -e " ${CYAN}${name}${RESET} : ${YELLOW}${current}${RESET} → ${GREEN}${available}${RESET}" + done <<< "$upgradable" + echo "" + + read -p "Appliquer la mise à jour de ces ${count} paquet(s) ? [y/n] : " confirm + case "$confirm" in + [yY]*) ;; + *) echo -e "${YELLOW}Annulé.${RESET}"; exit 0 ;; + esac + echo "" + echo -e "${BOLD}--- Application de apt full-upgrade ---${RESET}" +fi + +# Appliquer +DEBIAN_FRONTEND=noninteractive apt-get full-upgrade -y -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" 2>&1 + +# Vérifier reboot requis +reboot_required=false +if [ -f /var/run/reboot-required ]; then + reboot_required=true +fi + +if $INTERACTIVE; then + echo "" + echo -e "${GREEN}✅ Mise à jour terminée.${RESET}" + if $reboot_required; then + echo -e "${RED}⚠️ Redémarrage requis.${RESET}" + fi +else + printf '{"status":"upgraded","upgraded":%d,"reboot_required":%s}\n' \ + "$count" "$reboot_required" +fi