diff --git a/README.md b/README.md index 58b473c..e58c09f 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,69 @@ docker compose up -d --build L'application est ensuite disponible sur `http://localhost:8080`. +## Déploiement dans un LXC Proxmox + +Deux scripts Bash permettent de créer un conteneur LXC Debian sur Proxmox puis de le mettre à jour depuis Git. + +Prérequis sur la machine qui lance les scripts : + +- `ssh` +- `sshpass` + +Le déploiement dans le LXC n'utilise pas Docker. Le script installe `nginx`, `git` et `rsync` dans le conteneur, clone le dépôt puis publie uniquement les fichiers web. + +### Installer un nouveau LXC + +```bash +./scripts/install-proxmox-lxc.sh \ + --proxmox-host 10.0.0.2 \ + --proxmox-user root@pam \ + --proxmox-password 'secret' +``` + +Valeurs par défaut utiles : + +- LXC nommé `chesscubing-web` +- IP du LXC en `dhcp` +- branche Git `main` +- dépôt `https://git.jeannerot.fr/christophe/chesscubing.git` + +Options utiles si besoin : + +- `--ctid 120` +- `--lxc-ip 192.168.1.50/24 --gateway 192.168.1.1` +- `--template-storage local` +- `--rootfs-storage local-lvm` +- `--branch main` + +À la fin, le script affiche : + +- le `CTID` +- le mot de passe `root` du LXC +- l'URL probable du site + +### Mettre à jour depuis Git + +```bash +./scripts/update-proxmox-lxc.sh \ + --proxmox-host 10.0.0.2 \ + --proxmox-user root@pam \ + --proxmox-password 'secret' \ + --ctid 120 +``` + +On peut aussi cibler le conteneur par nom si on n'a pas le `CTID` : + +```bash +./scripts/update-proxmox-lxc.sh \ + --proxmox-host 10.0.0.2 \ + --proxmox-user root@pam \ + --proxmox-password 'secret' \ + --hostname chesscubing-web +``` + +Le script de mise à jour exécute un `git pull --ff-only` dans le conteneur puis republie les fichiers statiques via `nginx`. + ## Fichiers clés - `index.html` : page d'accueil du site @@ -38,3 +101,5 @@ L'application est ensuite disponible sur `http://localhost:8080`. - `styles.css` : design mobile/tablette - `app.js` : logique de match et arbitrage - `docker-compose.yml` + `Dockerfile` : exécution locale +- `scripts/install-proxmox-lxc.sh` : création et déploiement d'un LXC Proxmox +- `scripts/update-proxmox-lxc.sh` : mise à jour d'un LXC existant depuis Git diff --git a/scripts/install-proxmox-lxc.sh b/scripts/install-proxmox-lxc.sh new file mode 100755 index 0000000..b29d5e8 --- /dev/null +++ b/scripts/install-proxmox-lxc.sh @@ -0,0 +1,437 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + ./scripts/install-proxmox-lxc.sh \ + --proxmox-host 192.168.1.10 \ + --proxmox-user root@pam \ + --proxmox-password 'motdepasse' + +Options principales: + --proxmox-host IP ou nom DNS du serveur Proxmox + --proxmox-user Utilisateur SSH Proxmox (defaut: root@pam) + --proxmox-password Mot de passe SSH Proxmox + --ssh-port Port SSH Proxmox (defaut: 22) + --ctid CTID Proxmox. Si vide, le prochain ID libre est utilise + --hostname Nom du LXC (defaut: chesscubing-web) + --lxc-ip IP du LXC ou 'dhcp' (defaut: dhcp) + --gateway Passerelle si IP statique + --bridge Bridge reseau Proxmox (defaut: vmbr0) + --cores Nombre de vCPU du LXC (defaut: 2) + --memory Memoire RAM en Mo (defaut: 1024) + --swap Swap en Mo (defaut: 512) + --disk-gb Taille disque du LXC en Go (defaut: 6) + --template-storage Stockage Proxmox pour les templates + --rootfs-storage Stockage Proxmox pour le disque LXC + --repo-url Depot Git a deployer + --branch Branche Git a deployer (defaut: main) + --lxc-password Mot de passe root du LXC. Genere si absent + -h, --help Affiche cette aide + +Exemple: + ./scripts/install-proxmox-lxc.sh \ + --proxmox-host 10.0.0.2 \ + --proxmox-user root@pam \ + --proxmox-password 'secret' +EOF +} + +die() { + printf 'Erreur: %s\n' "$*" >&2 + exit 1 +} + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || die "La commande '$1' est requise." +} + +PROXMOX_HOST="" +PROXMOX_USER="root@pam" +PROXMOX_PASSWORD="${PROXMOX_PASSWORD:-}" +PROXMOX_PORT="22" + +CTID="" +LXC_HOSTNAME="chesscubing-web" +LXC_IP="dhcp" +LXC_GATEWAY="" +LXC_BRIDGE="vmbr0" +LXC_CORES="2" +LXC_MEMORY="1024" +LXC_SWAP="512" +LXC_DISK_GB="6" +TEMPLATE_STORAGE="" +ROOTFS_STORAGE="" +REPO_URL="https://git.jeannerot.fr/christophe/chesscubing.git" +REPO_BRANCH="main" +LXC_PASSWORD="" + +while [[ $# -gt 0 ]]; do + case "$1" in + --proxmox-host) + PROXMOX_HOST="${2:-}" + shift 2 + ;; + --proxmox-user) + PROXMOX_USER="${2:-}" + shift 2 + ;; + --proxmox-password) + PROXMOX_PASSWORD="${2:-}" + shift 2 + ;; + --ssh-port) + PROXMOX_PORT="${2:-}" + shift 2 + ;; + --ctid) + CTID="${2:-}" + shift 2 + ;; + --hostname) + LXC_HOSTNAME="${2:-}" + shift 2 + ;; + --lxc-ip) + LXC_IP="${2:-}" + shift 2 + ;; + --gateway) + LXC_GATEWAY="${2:-}" + shift 2 + ;; + --bridge) + LXC_BRIDGE="${2:-}" + shift 2 + ;; + --cores) + LXC_CORES="${2:-}" + shift 2 + ;; + --memory) + LXC_MEMORY="${2:-}" + shift 2 + ;; + --swap) + LXC_SWAP="${2:-}" + shift 2 + ;; + --disk-gb) + LXC_DISK_GB="${2:-}" + shift 2 + ;; + --template-storage) + TEMPLATE_STORAGE="${2:-}" + shift 2 + ;; + --rootfs-storage) + ROOTFS_STORAGE="${2:-}" + shift 2 + ;; + --repo-url) + REPO_URL="${2:-}" + shift 2 + ;; + --branch) + REPO_BRANCH="${2:-}" + shift 2 + ;; + --lxc-password) + LXC_PASSWORD="${2:-}" + shift 2 + ;; + -h | --help) + usage + exit 0 + ;; + *) + die "Option inconnue: $1" + ;; + esac +done + +[[ -n "$PROXMOX_HOST" ]] || die "Merci de fournir --proxmox-host." +[[ -n "$PROXMOX_USER" ]] || die "Merci de fournir --proxmox-user." + +if [[ -z "$PROXMOX_PASSWORD" ]]; then + read -rsp "Mot de passe SSH pour ${PROXMOX_USER}@${PROXMOX_HOST}: " PROXMOX_PASSWORD + echo +fi + +need_cmd ssh +need_cmd sshpass + +printf 'Creation du LXC "%s" sur %s...\n' "$LXC_HOSTNAME" "$PROXMOX_HOST" + +sshpass -p "$PROXMOX_PASSWORD" \ + ssh \ + -p "$PROXMOX_PORT" \ + -o StrictHostKeyChecking=accept-new \ + -o PreferredAuthentications=password \ + -o PubkeyAuthentication=no \ + "$PROXMOX_USER@$PROXMOX_HOST" \ + bash -s -- \ + "$CTID" \ + "$LXC_HOSTNAME" \ + "$LXC_IP" \ + "$LXC_GATEWAY" \ + "$LXC_BRIDGE" \ + "$LXC_CORES" \ + "$LXC_MEMORY" \ + "$LXC_SWAP" \ + "$LXC_DISK_GB" \ + "$TEMPLATE_STORAGE" \ + "$ROOTFS_STORAGE" \ + "$REPO_URL" \ + "$REPO_BRANCH" \ + "$LXC_PASSWORD" <<'REMOTE' +set -euo pipefail + +ctid="$1" +lxc_hostname="$2" +lxc_ip="$3" +lxc_gateway="$4" +lxc_bridge="$5" +lxc_cores="$6" +lxc_memory="$7" +lxc_swap="$8" +lxc_disk_gb="$9" +shift 9 +template_storage="$1" +rootfs_storage="$2" +repo_url="$3" +repo_branch="$4" +lxc_password="$5" + +die() { + printf 'Erreur: %s\n' "$*" >&2 + exit 1 +} + +need_remote_cmd() { + command -v "$1" >/dev/null 2>&1 || die "La commande '$1' est absente sur Proxmox." +} + +pick_storage() { + local candidate="" + + for candidate in "$@"; do + if pvesm status 2>/dev/null | awk 'NR > 1 { print $1 }' | grep -Fxq "$candidate"; then + printf '%s\n' "$candidate" + return 0 + fi + done + + return 1 +} + +pick_first_dir_storage() { + pvesm status 2>/dev/null | awk 'NR > 1 && $2 == "dir" { print $1; exit }' +} + +find_ctid_by_hostname() { + local wanted="$1" + local candidate="" + local candidate_hostname="" + + while read -r candidate; do + [[ -n "$candidate" ]] || continue + candidate_hostname="$(pct config "$candidate" 2>/dev/null | awk -F ': ' '/^hostname:/ { print $2; exit }')" + if [[ "$candidate_hostname" == "$wanted" ]]; then + printf '%s\n' "$candidate" + return 0 + fi + done < <(pct list | awk 'NR > 1 { print $1 }') + + return 1 +} + +ct_exec() { + pct exec "$ctid" -- bash -lc "$1" +} + +need_remote_cmd pct +need_remote_cmd pveam +need_remote_cmd pvesm + +if [[ -z "$ctid" ]]; then + command -v pvesh >/dev/null 2>&1 || die "Impossible de calculer le prochain CTID sans pvesh." + ctid="$(pvesh get /cluster/nextid)" +fi + +if pct status "$ctid" >/dev/null 2>&1; then + die "Le CTID $ctid existe deja." +fi + +if existing_ctid="$(find_ctid_by_hostname "$lxc_hostname")"; then + die "Un conteneur nomme $lxc_hostname existe deja sous le CTID $existing_ctid." +fi + +if [[ -z "$template_storage" ]]; then + template_storage="$(pick_storage local)" + if [[ -z "$template_storage" ]]; then + template_storage="$(pick_first_dir_storage)" + fi +fi + +[[ -n "$template_storage" ]] || die "Aucun stockage de template detecte. Passe --template-storage." + +if [[ -z "$rootfs_storage" ]]; then + rootfs_storage="$(pick_storage local-lvm local-zfs local)" +fi + +[[ -n "$rootfs_storage" ]] || die "Aucun stockage rootfs detecte. Passe --rootfs-storage." + +printf 'Mise a jour du catalogue de templates Proxmox...\n' +pveam update >/dev/null + +template_name="$(pveam available --section system | awk '/debian-12-standard/ { print $2 }' | sort -V | tail -n 1)" +if [[ -z "$template_name" ]]; then + template_name="debian-12-standard_12.7-1_amd64.tar.zst" +fi + +if ! pveam list "$template_storage" 2>/dev/null | grep -Fq "$template_name"; then + printf 'Telechargement du template %s sur %s...\n' "$template_name" "$template_storage" + pveam download "$template_storage" "$template_name" >/dev/null +fi + +template_ref="${template_storage}:vztmpl/${template_name}" + +if [[ -z "$lxc_password" ]]; then + lxc_password="$(tr -dc 'A-Za-z0-9' /dev/null 2>&1; then + break + fi + sleep 2 +done + +pct exec "$ctid" -- true >/dev/null 2>&1 || die "Le LXC n'est pas joignable apres le demarrage." + +printf 'Installation de nginx, git et rsync dans le conteneur...\n' +ct_exec "apt-get update && apt-get install -y ca-certificates git nginx rsync" + +ct_exec "install -d -m 0755 /opt/chesscubing/repo /var/www/chesscubing/current" + +printf 'Clonage du depot %s...\n' "$repo_url" +ct_exec "if [ ! -d /opt/chesscubing/repo/.git ]; then \ + rm -rf /opt/chesscubing/repo/* /opt/chesscubing/repo/.[!.]* /opt/chesscubing/repo/..?* 2>/dev/null || true; \ + git clone --branch '$repo_branch' --single-branch '$repo_url' /opt/chesscubing/repo; \ +else \ + cd /opt/chesscubing/repo && git checkout '$repo_branch' && git pull --ff-only origin '$repo_branch'; \ +fi" + +ct_exec "cat > /usr/local/bin/update-chesscubing <<'SCRIPT' +#!/usr/bin/env bash +set -euo pipefail + +repo_dir='/opt/chesscubing/repo' +web_root='/var/www/chesscubing/current' +branch=\"\${1:-${repo_branch}}\" + +cd \"\$repo_dir\" + +if ! git diff --quiet || ! git diff --cached --quiet; then + echo 'Le depot de production contient des modifications locales. Mise a jour annulee.' >&2 + exit 1 +fi + +current_branch=\"\$(git rev-parse --abbrev-ref HEAD)\" +if [[ \"\$current_branch\" != \"\$branch\" ]]; then + git checkout \"\$branch\" +fi + +git fetch origin \"\$branch\" +git pull --ff-only origin \"\$branch\" + +install -d -m 0755 \"\$web_root\" + +rsync -a --delete \ + --include='*/' \ + --include='*.html' \ + --include='*.css' \ + --include='*.js' \ + --include='*.png' \ + --include='*.jpg' \ + --include='*.jpeg' \ + --include='*.svg' \ + --include='*.webp' \ + --include='*.ico' \ + --include='*.pdf' \ + --exclude='*' \ + \"\$repo_dir/\" \"\$web_root/\" + +chown -R www-data:www-data \"\$web_root\" + +nginx -t +systemctl reload nginx +SCRIPT +chmod +x /usr/local/bin/update-chesscubing" + +ct_exec "cat > /etc/nginx/sites-available/chesscubing.conf <<'NGINX' +server { + listen 80; + listen [::]:80; + server_name _; + + root /var/www/chesscubing/current; + index index.html; + + location / { + try_files \$uri \$uri/ /index.html; + } + + location ~* \.(?:css|js|png|jpg|jpeg|svg|webp|ico|pdf)$ { + expires 7d; + add_header Cache-Control 'public, max-age=604800'; + } +} +NGINX +rm -f /etc/nginx/sites-enabled/default +ln -sf /etc/nginx/sites-available/chesscubing.conf /etc/nginx/sites-enabled/chesscubing.conf" + +printf 'Publication du site dans le LXC...\n' +ct_exec "/usr/local/bin/update-chesscubing '$repo_branch'" +ct_exec "systemctl enable nginx >/dev/null && systemctl restart nginx" + +container_ip="$(pct exec "$ctid" -- bash -lc "hostname -I | awk '{print \$1}'" 2>/dev/null | tr -d '\r' || true)" + +cat <} + +Pour mettre a jour l'application plus tard: + ./scripts/update-proxmox-lxc.sh --proxmox-host --proxmox-user --proxmox-password '' --ctid $ctid +EOF +REMOTE diff --git a/scripts/update-proxmox-lxc.sh b/scripts/update-proxmox-lxc.sh new file mode 100755 index 0000000..2d318a4 --- /dev/null +++ b/scripts/update-proxmox-lxc.sh @@ -0,0 +1,170 @@ +#!/usr/bin/env bash +set -euo pipefail + +usage() { + cat <<'EOF' +Usage: + ./scripts/update-proxmox-lxc.sh \ + --proxmox-host 192.168.1.10 \ + --proxmox-user root@pam \ + --proxmox-password 'motdepasse' \ + --ctid 120 + +Options principales: + --proxmox-host IP ou nom DNS du serveur Proxmox + --proxmox-user Utilisateur SSH Proxmox (defaut: root@pam) + --proxmox-password Mot de passe SSH Proxmox + --ssh-port Port SSH Proxmox (defaut: 22) + --ctid CTID du LXC a mettre a jour + --hostname Nom du LXC si le CTID n'est pas fourni (defaut: chesscubing-web) + --branch Branche Git a deployer (defaut: main) + -h, --help Affiche cette aide +EOF +} + +die() { + printf 'Erreur: %s\n' "$*" >&2 + exit 1 +} + +need_cmd() { + command -v "$1" >/dev/null 2>&1 || die "La commande '$1' est requise." +} + +PROXMOX_HOST="" +PROXMOX_USER="root@pam" +PROXMOX_PASSWORD="${PROXMOX_PASSWORD:-}" +PROXMOX_PORT="22" + +CTID="" +LXC_HOSTNAME="chesscubing-web" +REPO_BRANCH="main" + +while [[ $# -gt 0 ]]; do + case "$1" in + --proxmox-host) + PROXMOX_HOST="${2:-}" + shift 2 + ;; + --proxmox-user) + PROXMOX_USER="${2:-}" + shift 2 + ;; + --proxmox-password) + PROXMOX_PASSWORD="${2:-}" + shift 2 + ;; + --ssh-port) + PROXMOX_PORT="${2:-}" + shift 2 + ;; + --ctid) + CTID="${2:-}" + shift 2 + ;; + --hostname) + LXC_HOSTNAME="${2:-}" + shift 2 + ;; + --branch) + REPO_BRANCH="${2:-}" + shift 2 + ;; + -h | --help) + usage + exit 0 + ;; + *) + die "Option inconnue: $1" + ;; + esac +done + +[[ -n "$PROXMOX_HOST" ]] || die "Merci de fournir --proxmox-host." +[[ -n "$PROXMOX_USER" ]] || die "Merci de fournir --proxmox-user." + +if [[ -z "$PROXMOX_PASSWORD" ]]; then + read -rsp "Mot de passe SSH pour ${PROXMOX_USER}@${PROXMOX_HOST}: " PROXMOX_PASSWORD + echo +fi + +need_cmd ssh +need_cmd sshpass + +printf 'Mise a jour du LXC ChessCubing sur %s...\n' "$PROXMOX_HOST" + +sshpass -p "$PROXMOX_PASSWORD" \ + ssh \ + -p "$PROXMOX_PORT" \ + -o StrictHostKeyChecking=accept-new \ + -o PreferredAuthentications=password \ + -o PubkeyAuthentication=no \ + "$PROXMOX_USER@$PROXMOX_HOST" \ + bash -s -- \ + "$CTID" \ + "$LXC_HOSTNAME" \ + "$REPO_BRANCH" <<'REMOTE' +set -euo pipefail + +ctid="$1" +lxc_hostname="$2" +repo_branch="$3" + +die() { + printf 'Erreur: %s\n' "$*" >&2 + exit 1 +} + +find_ctid_by_hostname() { + local wanted="$1" + local candidate="" + local candidate_hostname="" + + while read -r candidate; do + [[ -n "$candidate" ]] || continue + candidate_hostname="$(pct config "$candidate" 2>/dev/null | awk -F ': ' '/^hostname:/ { print $2; exit }')" + if [[ "$candidate_hostname" == "$wanted" ]]; then + printf '%s\n' "$candidate" + return 0 + fi + done < <(pct list | awk 'NR > 1 { print $1 }') + + return 1 +} + +command -v pct >/dev/null 2>&1 || die "La commande 'pct' est absente sur Proxmox." + +if [[ -z "$ctid" ]]; then + ctid="$(find_ctid_by_hostname "$lxc_hostname" || true)" +fi + +[[ -n "$ctid" ]] || die "Impossible de retrouver le LXC. Passe --ctid ou --hostname." + +if ! pct status "$ctid" >/dev/null 2>&1; then + die "Le conteneur $ctid est introuvable." +fi + +detected_hostname="$(pct config "$ctid" 2>/dev/null | awk -F ': ' '/^hostname:/ { print $2; exit }')" +if [[ -n "$detected_hostname" ]]; then + lxc_hostname="$detected_hostname" +fi + +if ! pct status "$ctid" | grep -q "running"; then + pct start "$ctid" + sleep 5 +fi + +pct exec "$ctid" -- test -x /usr/local/bin/update-chesscubing || die "Le script /usr/local/bin/update-chesscubing est absent dans le conteneur." + +pct exec "$ctid" -- /usr/local/bin/update-chesscubing "$repo_branch" + +container_ip="$(pct exec "$ctid" -- bash -lc "hostname -I | awk '{print \$1}'" 2>/dev/null | tr -d '\r' || true)" + +cat <} +EOF +REMOTE