1
0
postauto/update.sh

408 lines
14 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env bash
# exiger bash, bloquer le "source"
[ -n "${BASH_VERSION:-}" ] || exec bash "$0" "$@"
(return 0 2>/dev/null) && { echo "❌ Ne source pas ce script, exécute-le: ./$0"; return 1; }
# chemin canonique du script (robuste: ./script.sh, bash script.sh, via PATH, symlink…)
SCRIPT_SOURCE="${BASH_SOURCE[0]}"
SCRIPT_DIR="$(cd -- "$(dirname -- "$SCRIPT_SOURCE")" && pwd -P)"
SCRIPT_FILE="$SCRIPT_DIR/$(basename -- "$SCRIPT_SOURCE")"
set -Eeuo pipefail
trap 'echo "❌ ERREUR ligne $LINENO: $BASH_COMMAND" >&2' ERR
# ────────── Helpers & couleurs ──────────
if [ -t 1 ]; then
ROUGE='\e[31m'; VERT='\e[32m'; JAUNE='\e[33m'; BLEU='\e[34m'; NORMAL='\e[0m'
else
ROUGE=''; VERT=''; JAUNE=''; BLEU=''; NORMAL=''
fi
log() { printf "${BLEU}%s${NORMAL}\n" "$*"; }
ok() { printf "${VERT}%s${NORMAL}\n" "$*"; }
warn() { printf "${JAUNE}%s${NORMAL}\n" "$*"; }
err() { printf "${ROUGE}%s${NORMAL}\n" "$*"; }
die() { err "$*"; exit 1; }
install_bin(){ install -m 755 "$1" "$2"; }
# --- lire une clé JS (ligne "clé: valeur") sans exécuter ---
parse_js_raw() {
local key="$1"
sed -n -E "s/^[[:space:]]*['\"]?${key}['\"]?[[:space:]]*:[[:space:]]*(.*)$/\1/p" "$CFG_JS" \
| head -n1 | sed -E "s/[[:space:]]*(,)?[[:space:]]*$//"
}
# --- normaliser une valeur JS simple: enlève guillemets, garde nombres, laisse path.join tel quel ---
# --- normaliser une valeur JS simple ---
# - supprime les commentaires inline " // ... "
# - supprime la virgule terminale
# - trim espaces
# - retire guillemets si présents
normalize_js_value() {
local raw="$1"
# retire commentaire inline: seulement si précédé d'un espace (évite "https://")
raw="$(printf '%s' "$raw" | sed -E 's@[[:space:]]//.*$@@')"
# retire virgule en fin de champ et espaces résiduels
raw="$(printf '%s' "$raw" | sed -E 's/,[[:space:]]*$//')"
raw="$(printf '%s' "$raw" | sed -E 's/^[[:space:]]+//; s/[[:space:]]+$//')"
# retire guillemets simples/doubles
if [[ "$raw" =~ ^\"(.*)\"$ ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"; return
fi
if [[ "$raw" =~ ^\'(.*)\'$ ]]; then
printf '%s\n' "${BASH_REMATCH[1]}"; return
fi
printf '%s\n' "$raw"
}
# placeholders à refuser (vides, “Voir…”, “CHANGEME…”, etc.)
is_placeholder() {
local v="$1"
[[ -z "$v" ]] && return 0
[[ "$v" =~ ^(Voir|voir|Nom|change|CHANGE|changeme|CHANGEME|todo|TODO|example|your|/path/to|A[[:space:]]*NOUS|A[[:space:]]*RETROUVER) ]] && return 0
return 1
}
# entier (>=0)
is_int() { [[ "$1" =~ ^[0-9]+$ ]]; }
# booléen JS (true/false), avec ou sans guillemets
is_bool_literal() {
local v="$(echo "$1" | tr '[:upper:]' '[:lower:]')"
[[ "$v" == "true" || "$v" == "false" ]]
}
# ────────── Paths ──────────
BIN_DIR="$HOME/bin"
AUTOPOST_DIR="$HOME/autopost"
BASHRC_FILE="$HOME/.bashrc"
BASH_COMPLETION_DIR="$HOME/.bash_completion.d"
CONF_SH="$AUTOPOST_DIR/conf.sh"
CFG_JS="$AUTOPOST_DIR/config.js"
mkdir -p "$BIN_DIR" "$AUTOPOST_DIR" "$BASH_COMPLETION_DIR" "$AUTOPOST_DIR/views" "$AUTOPOST_DIR/public"
TMP_DIR="$(mktemp -d)"
cleanup(){ rm -rf "$TMP_DIR"; }
trap cleanup EXIT
updated=0
errors=0
# ────────── MAJ fichiers distants ──────────
declare -A FILES
FILES["$AUTOPOST_DIR/analyzer.sh"]="https://tig.unfr.pw/UNFR/postauto/raw/branch/main/autopost/analyzer.sh"
FILES["$AUTOPOST_DIR/posteur.sh"]="https://tig.unfr.pw/UNFR/postauto/raw/branch/main/autopost/posteur.sh"
FILES["$AUTOPOST_DIR/common.sh"]="https://tig.unfr.pw/UNFR/postauto/raw/branch/main/autopost/common.sh"
FILES["$BIN_DIR/postauto"]="https://tig.unfr.pw/UNFR/postauto/raw/branch/main/bin/postauto"
FILES["$AUTOPOST_DIR/server.js"]="https://tig.unfr.pw/UNFR/postauto/raw/branch/main/autopost/server.js"
FILES["$AUTOPOST_DIR/public/autopost.js"]="https://tig.unfr.pw/UNFR/postauto/raw/branch/main/autopost/public/autopost.js"
FILES["$AUTOPOST_DIR/views/autopost.html"]="https://tig.unfr.pw/UNFR/postauto/raw/branch/main/autopost/views/autopost.html"
log "Vérification/MAJ des fichiers…"
for LOCAL in "${!FILES[@]}"; do
URL="${FILES[$LOCAL]}"
TMP="$TMP_DIR/$(basename "$LOCAL").dl"
curl -fsSL "$URL" -o "$TMP" || die "Téléchargement échoué: $URL"
if [ ! -f "$LOCAL" ] || ! cmp -s "$LOCAL" "$TMP"; then
cp -f "$LOCAL" "$LOCAL.bak" 2>/dev/null || true
case "$LOCAL" in
*postauto|*.sh) install_bin "$TMP" "$LOCAL" ;;
*) install -m 644 "$TMP" "$LOCAL" ;;
esac
ok "Mise à jour: $LOCAL"
updated=1
fi
done
# ────────── Déplacer la complétion vers ~/.bash_completion.d & supprimer l'ancien bloc ──────────
DEBUT_MARKER="# DEBUT COMPLETION POSTAUTO"
FIN_MARKER="# FIN COMPLETION POSTAUTO"
if [ -f "$BASHRC_FILE" ] && grep -q "$DEBUT_MARKER" "$BASHRC_FILE"; then
log "Suppression de l'ancien bloc de complétion dans $BASHRC_FILE"
cp "$BASHRC_FILE" "${BASHRC_FILE}.bak"
sed -i "/$DEBUT_MARKER/,/$FIN_MARKER/d" "$BASHRC_FILE"
ok "Ancien bloc supprimé."
updated=1
fi
COMP_FILE="$BASH_COMPLETION_DIR/postauto"
COMPLETION_CODE=$(cat <<'EOF'
# completion postauto
_autopost_completion() {
local cur prev opts
cur="${COMP_WORDS[COMP_CWORD]}"
prev="${COMP_WORDS[COMP_CWORD-1]}"
opts="start stop restart show status createdb add log check update"
if [ $COMP_CWORD -eq 1 ]; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") ); return 0
fi
if [ $COMP_CWORD -eq 2 ] && [ "${COMP_WORDS[1]}" = "add" ]; then
COMPREPLY=( $(compgen -f -- "${cur}") ); return 0
fi
}
complete -F _autopost_completion postauto
EOF
)
if [ ! -s "$COMP_FILE" ] || ! cmp -s <(printf "%s" "$COMPLETION_CODE") "$COMP_FILE"; then
printf "%s" "$COMPLETION_CODE" > "$COMP_FILE"
ok "Completion installée: $COMP_FILE"
# hook .bashrc si pas déjà présent
grep -q '\.bash_completion.d/postauto' "$BASHRC_FILE" 2>/dev/null || \
echo '[ -f "$HOME/.bash_completion.d/postauto" ] && . "$HOME/.bash_completion.d/postauto"' >> "$BASHRC_FILE"
updated=1
fi
# ────────── Outils externes (optionnel: installe si manquants) ──────────
ensure_cmd(){ command -v "$1" >/dev/null 2>&1; }
if ! ensure_cmd 7z; then
log "Installation 7z…"
pushd "$TMP_DIR" >/dev/null
wget -q -o /dev/null -O 7z.tar.xz "https://7-zip.org/a/7z2409-linux-x64.tar.xz"
tar -xJf 7z.tar.xz
ZBIN="$(find . -maxdepth 1 -type f -name '7zz*' -perm -u+x | head -n1)"
[ -n "$ZBIN" ] || die "Binaire 7z introuvable"
install_bin "$ZBIN" "$BIN_DIR/7z"
popd >/dev/null
fi
if ! ensure_cmd BDInfo; then
log "Installation BDInfo…"
pushd "$TMP_DIR" >/dev/null
wget -q -o /dev/null -O bdinfo.zip "https://github.com/dotnetcorecorner/BDInfo/releases/download/linux-2.0.6/bdinfo_linux_v2.0.6.zip"
unzip -q bdinfo.zip
BDBIN="$(find . -type f -name BDInfo -perm -u+x | head -n1)"
[ -n "$BDBIN" ] || die "BDInfo introuvable"
install_bin "$BDBIN" "$BIN_DIR/BDInfo"
popd >/dev/null
fi
if ! ensure_cmd BDInfoDataSubstractor; then
log "Installation BDInfoDataSubstractor…"
pushd "$TMP_DIR" >/dev/null
wget -q -o /dev/null -O substractor.zip "https://github.com/dotnetcorecorner/BDInfo/releases/download/linux-2.0.6/bdinfodatasubstractor_linux_v2.0.6.zip"
unzip -q substractor.zip
SBBIN="$(find . -type f -name BDInfoDataSubstractor -perm -u+x | head -n1)"
[ -n "$SBBIN" ] || die "BDInfoDataSubstractor introuvable"
install_bin "$SBBIN" "$BIN_DIR/BDInfoDataSubstractor"
popd >/dev/null
fi
# Modules npm locaux dans $AUTOPOST_DIR
log "Vérification modules npm"
pushd "$AUTOPOST_DIR" >/dev/null
[ -f package.json ] || npm init -y >/dev/null 2>&1 || true
modules=(express express-session sqlite3 ansi-to-html @tailwindcss/browser autoprefixer jquery mysql2 session-file-store chokidar)
missing=()
for m in "${modules[@]}"; do
npm list "$m" --depth=0 >/dev/null 2>&1 || missing+=("$m")
done
if [ "${#missing[@]}" -gt 0 ]; then
log "Installation modules: ${missing[*]}"
npm install "${missing[@]}"
else
ok "Tous les modules npm sont présents"
fi
popd >/dev/null
# ────────── VALIDATION conf.sh (sans exécuter) ──────────
# --- conf.sh : validation déclarative ---
check_conf() {
local file="$CONF_SH"
[[ -f "$file" ]] || { err "Manquant: $file"; errors=$((errors+1)); return; }
log "Validation déclarative de $file"
# Parse simple NAME=VALUE (ignore commentaires / 'export')
declare -A V=()
while IFS= read -r line; do
[[ "$line" =~ ^[[:space:]]*# ]] && continue
[[ "$line" =~ ^[[:space:]]*$ ]] && continue
line="${line#export }"
if [[ "$line" =~ ^[[:space:]]*([A-Za-z_][A-Za-z0-9_]*)[[:space:]]*=(.*)$ ]]; then
name="${BASH_REMATCH[1]}"
val="${BASH_REMATCH[2]}"
val="${val%%#*}"; val="${val%%;*}"
val="$(echo -n "$val" | sed -E "s/^[[:space:]]*['\"]?//; s/['\"]?[[:space:]]*$//")"
V["$name"]="$val"
fi
done < "$file"
# Requis généraux (non-placeholder)
for k in URL_API APIKEY DOSSIER_GLOBAL DOSSIER_NFO DOSSIER_LOGS DOSSIER_NZB_ATTENTE DOSSIER_NZB_FINAL MOVE_CMD MYSQL_TABLE dbtype; do
v="${V[$k]:-}"
if is_placeholder "$v"; then
err "conf.sh: '$k' non renseigné"; errors=$((errors+1))
fi
done
# MOVE_CMD valeurs autorisées
case "${V[MOVE_CMD]:-}" in
"cp -rl"|"cp -rs"|"ln -s"|"mv"|"cp") : ;;
*)
err "conf.sh: MOVE_CMD invalide ('${V[MOVE_CMD]:-}'), attendus: cp -rl|cp -rs|ln -s|mv|cp"
errors=$((errors+1))
;;
esac
# Fournisseur Usenet : non-vides + numériques où nécessaire
for k in NG_HOST NG_USER NG_PASS; do
if is_placeholder "${V[$k]:-}"; then
err "conf.sh: '$k' non renseigné"; errors=$((errors+1))
fi
done
if ! [[ "${V[NG_PORT]:-}" =~ ^[0-9]+$ ]]; then
err "conf.sh: NG_PORT doit être numérique"; errors=$((errors+1))
fi
if ! [[ "${V[NG_NBR_CONN]:-}" =~ ^[0-9]+$ ]]; then
err "conf.sh: NG_NBR_CONN doit être numérique"; errors=$((errors+1))
fi
# DB : règles conditionnelles (déclarations seulement)
case "${V[dbtype]:-}" in
sqlite)
if is_placeholder "${V[DB_FILE]:-}"; then
err "conf.sh: DB_FILE requis en mode sqlite"; errors=$((errors+1))
fi
;;
mysql)
for k in MYSQL_HOST MYSQL_USER MYSQL_PASS MYSQL_DB; do
if is_placeholder "${V[$k]:-}"; then
err "conf.sh: '$k' requis en mode mysql"; errors=$((errors+1))
fi
done
if ! [[ "${V[MYSQL_PORT]:-}" =~ ^[0-9]+$ ]]; then
err "conf.sh: MYSQL_PORT doit être numérique"; errors=$((errors+1))
fi
;;
*)
err "conf.sh: dbtype doit être 'sqlite' ou 'mysql' (actuel='${V[dbtype]:-}')"
errors=$((errors+1))
;;
esac
}
check_conf "$CONF_SH"
# ────────── VALIDATION config.js (avec Node) ──────────
# --- config.js : validation déclarative (sans exécuter du JS) ---
validate_config_js() {
[[ -f "$CFG_JS" ]] || { err "Manquant: $CFG_JS"; errors=$((errors+1)); return; }
log "Validation déclarative de $CFG_JS"
# valeurs principales
local dbtype port name secret table
dbtype="$(normalize_js_value "$(parse_js_raw dbtype)")"
port="$(normalize_js_value "$(parse_js_raw port)")"
name="$(normalize_js_value "$(parse_js_raw name)")"
secret="$(normalize_js_value "$(parse_js_raw sessionSecret)")"
table="$(normalize_js_value "$(parse_js_raw DB_TABLE)")"
# checks minimaux
if ! is_int "$port" || (( port < 1 || port > 65535 )); then
err "config.js: 'port' invalide ($port)"; errors=$((errors+1))
fi
if is_placeholder "$name"; then err "config.js: 'name' non renseigné"; errors=$((errors+1)); fi
if is_placeholder "$secret"; then err "config.js: 'sessionSecret' non renseigné"; errors=$((errors+1)); fi
if is_placeholder "$table"; then err "config.js: 'DB_TABLE' non renseigné"; errors=$((errors+1)); fi
# dossiers : déclaration non vide (pas de test FS)
for key in finishdirectory logdirectory infodirectory; do
val="$(normalize_js_value "$(parse_js_raw "$key")")"
if is_placeholder "$val"; then
err "config.js: '$key' non renseigné"; errors=$((errors+1))
fi
done
# trustProxy / cookieSecure / sessionStorePath
local tp cs ssp
tp="$(normalize_js_value "$(parse_js_raw trustProxy)")"
cs="$(normalize_js_value "$(parse_js_raw cookieSecure)")"
ssp="$(normalize_js_value "$(parse_js_raw sessionStorePath)")"
if is_placeholder "$tp"; then
err "config.js: 'trustProxy' non renseigné"; errors=$((errors+1))
else
if is_int "$tp"; then
if (( tp < 0 )); then
err "config.js: 'trustProxy' doit être >= 0 (valeur=$tp)"; errors=$((errors+1))
fi
else
# chaîne non vide acceptée (ex: "loopback,uniquelocal")
:
fi
fi
if ! is_bool_literal "$cs"; then
err "config.js: 'cookieSecure' doit être true ou false (valeur='$cs')"; errors=$((errors+1))
fi
if is_placeholder "$ssp"; then
err "config.js: 'sessionStorePath' non renseigné"; errors=$((errors+1))
fi
# règles DB (déclarations uniquement)
case "$dbtype" in
sqlite)
val="$(normalize_js_value "$(parse_js_raw dbFile)")"
if is_placeholder "$val"; then
err "config.js: 'dbFile' requis (sqlite)"; errors=$((errors+1))
fi
;;
mysql)
local H P U PW DB
H="$(normalize_js_value "$(parse_js_raw DB_HOST)")"
P="$(normalize_js_value "$(parse_js_raw DB_PORT)")"
U="$(normalize_js_value "$(parse_js_raw DB_USER)")"
PW="$(normalize_js_value "$(parse_js_raw DB_PASSWORD)")"
DB="$(normalize_js_value "$(parse_js_raw DB_DATABASE)")"
if is_placeholder "$H"; then err "config.js: 'DB_HOST' requis (mysql)"; errors=$((errors+1)); fi
if ! is_int "$P"; then err "config.js: 'DB_PORT' entier requis (mysql)"; errors=$((errors+1)); fi
if is_placeholder "$U"; then err "config.js: 'DB_USER' requis (mysql)"; errors=$((errors+1)); fi
if is_placeholder "$PW"; then err "config.js: 'DB_PASSWORD' requis (mysql)"; errors=$((errors+1)); fi
if is_placeholder "$DB"; then err "config.js: 'DB_DATABASE' requis (mysql)"; errors=$((errors+1)); fi
;;
*)
err "config.js: 'dbtype' doit être 'sqlite' ou 'mysql' (actuel='$dbtype')"
errors=$((errors+1))
;;
esac
}
validate_config_js
# ────────── Résumé & exit codes ──────────
if [ "$updated" -eq 1 ]; then
warn "Mises à jour appliquées — relance: 'postauto restart'"
fi
if [ "$errors" -gt 0 ]; then
err "Des problèmes de configuration ont été détectés (${errors}). Corrige-les puis relance lupdate."
exit 2
else
ok "Configuration OK."
fi
# auto-suppression (désactivable via SKIP_SELF_DELETE=1)
if [ -z "${SKIP_SELF_DELETE:-}" ] && [ -f "$SCRIPT_FILE" ]; then
if rm -f -- "$SCRIPT_FILE"; then
ok "Script supprimé: $SCRIPT_FILE"
else
warn "Impossible de supprimer $SCRIPT_FILE (droits sur le dossier ?)"
fi
fi