From d36da7c9936b3f580168cba6fd8bbfdc7f6e7ef5 Mon Sep 17 00:00:00 2001 From: Christophe Date: Tue, 14 Apr 2026 20:32:39 +0200 Subject: [PATCH] Aligne le deploiement Proxmox sur la stack Docker complete --- .env.example | 2 + README.md | 7 +- docker-compose.yml | 4 +- scripts/install-proxmox-lxc.sh | 391 ++++++++++++++++++++++----------- scripts/update-proxmox-lxc.sh | 391 +++++++++++++++++++++++---------- 5 files changed, 543 insertions(+), 252 deletions(-) diff --git a/.env.example b/.env.example index 32384d2..2cc0f98 100644 --- a/.env.example +++ b/.env.example @@ -1,3 +1,5 @@ +WEB_PORT=8080 +PUBLIC_BASE_URL=http://localhost:8080 KEYCLOAK_DB_NAME=keycloak KEYCLOAK_DB_USER=keycloak KEYCLOAK_DB_PASSWORD=change-me diff --git a/README.md b/README.md index cc32e11..43966f4 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Identifiants d'administration par defaut pour le premier demarrage local : Ces valeurs peuvent etre surchargees via les variables d'environnement de `.env.example`. La base MySQL du site utilise les variables `SITE_DB_*` du meme fichier. +Pour un deploiement hors localhost, `PUBLIC_BASE_URL` doit pointer vers l'URL publique du site et `WEB_PORT` vers le port HTTP expose. Au demarrage, le service `keycloak-init` resynchronise automatiquement le realm courant pour garder l'inscription active et autoriser le flux de connexion integre, meme si la base Keycloak existe deja. @@ -99,9 +100,11 @@ Prerrequis sur la machine qui lance les scripts : - en mode distant : `ssh` et `sshpass` - en mode local sur l'hote Proxmox : aucun paquet supplementaire n'est installe sur Proxmox -Le deploiement dans le LXC n'utilise pas Docker. Le script clone le depot, publie l'application Blazor dans le conteneur, puis sert le resultat via `nginx`. +Le deploiement dans le LXC Proxmox utilise maintenant Docker dans le conteneur pour lancer la meme stack qu'en local : `web`, `auth`, `keycloak`, `postgres` et `mysql`. -Attention : la pile Keycloak fournie ici est actuellement prete a l'emploi dans la stack Docker du projet. Les scripts LXC existants ne provisionnent pas encore automatiquement Keycloak ni sa base Postgres. +Le script prepare une URL publique pour Keycloak via `PUBLIC_BASE_URL`, installe Docker dans le LXC, puis lance `docker compose up -d --build`. + +Pour un usage confortable, il est recommande de prevoir un LXC avec au moins 2 vCPU, 4 Go de RAM et 10 Go de disque. ### Installer un nouveau LXC diff --git a/docker-compose.yml b/docker-compose.yml index cc51dad..68c59e2 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,7 +10,7 @@ services: keycloak: condition: service_started ports: - - "8080:80" + - "${WEB_PORT:-8080}:80" restart: unless-stopped auth: @@ -53,7 +53,7 @@ services: KC_BOOTSTRAP_ADMIN_USERNAME: ${KEYCLOAK_ADMIN_USER:-admin} KC_BOOTSTRAP_ADMIN_PASSWORD: ${KEYCLOAK_ADMIN_PASSWORD:-admin} KC_PROXY_HEADERS: xforwarded - KC_HOSTNAME: http://localhost:8080/auth + KC_HOSTNAME: "${PUBLIC_BASE_URL:-http://localhost:8080}/auth" KC_HTTP_RELATIVE_PATH: /auth KC_HOSTNAME_STRICT: "false" volumes: diff --git a/scripts/install-proxmox-lxc.sh b/scripts/install-proxmox-lxc.sh index 4c836e4..4ff92c7 100755 --- a/scripts/install-proxmox-lxc.sh +++ b/scripts/install-proxmox-lxc.sh @@ -25,9 +25,9 @@ Options principales: --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) + --memory Memoire RAM en Mo (defaut: 4096) + --swap Swap en Mo (defaut: 1024) + --disk-gb Taille disque du LXC en Go (defaut: 12) --template-storage Stockage Proxmox pour les templates --rootfs-storage Stockage Proxmox pour le disque LXC --repo-url Depot Git a deployer @@ -36,6 +36,11 @@ Options principales: --ethan-branch Branche Git de l'application Ethan (defaut: main) --brice-repo-url Depot Git de l'application Brice --brice-branch Branche Git de l'application Brice (defaut: main) + --public-base-url URL publique du site (ex: http://jeu.example.com) + --web-port Port HTTP expose dans le LXC (defaut: 80) + --keycloak-admin-user Utilisateur admin Keycloak (defaut: admin) + --keycloak-admin-password + Mot de passe admin Keycloak. Genere si absent --lxc-password Mot de passe root du LXC. Genere si absent -h, --help Affiche cette aide @@ -68,9 +73,9 @@ LXC_IP="dhcp" LXC_GATEWAY="" LXC_BRIDGE="vmbr0" LXC_CORES="2" -LXC_MEMORY="1024" -LXC_SWAP="512" -LXC_DISK_GB="6" +LXC_MEMORY="4096" +LXC_SWAP="1024" +LXC_DISK_GB="12" TEMPLATE_STORAGE="" ROOTFS_STORAGE="" REPO_URL="https://git.jeannerot.fr/christophe/chesscubing.git" @@ -79,6 +84,10 @@ ETHAN_REPO_URL="https://git.jeannerot.fr/Mineloulou/Chesscubing.git" ETHAN_REPO_BRANCH="main" BRICE_REPO_URL="https://git.jeannerot.fr/Lescratcheur/ChessCubing.git" BRICE_REPO_BRANCH="main" +PUBLIC_BASE_URL="" +WEB_PORT="80" +KEYCLOAK_ADMIN_USER="admin" +KEYCLOAK_ADMIN_PASSWORD="" LXC_PASSWORD="" while [[ $# -gt 0 ]]; do @@ -171,6 +180,22 @@ while [[ $# -gt 0 ]]; do BRICE_REPO_BRANCH="${2:-}" shift 2 ;; + --public-base-url) + PUBLIC_BASE_URL="${2:-}" + shift 2 + ;; + --web-port) + WEB_PORT="${2:-}" + shift 2 + ;; + --keycloak-admin-user) + KEYCLOAK_ADMIN_USER="${2:-}" + shift 2 + ;; + --keycloak-admin-password) + KEYCLOAK_ADMIN_PASSWORD="${2:-}" + shift 2 + ;; --lxc-password) LXC_PASSWORD="${2:-}" shift 2 @@ -221,6 +246,10 @@ ethan_repo_branch="$6" brice_repo_url="$7" brice_repo_branch="$8" lxc_password="$9" +public_base_url="${10}" +web_port="${11}" +keycloak_admin_user="${12}" +keycloak_admin_password="${13}" die() { printf 'Erreur: %s\n' "$*" >&2 @@ -339,7 +368,8 @@ pct create "$ctid" "$template_ref" \ --onboot 1 \ --ostype debian \ --password "$lxc_password" \ - --unprivileged 1 + --unprivileged 1 \ + --features nesting=1,keyctl=1 pct start "$ctid" @@ -353,21 +383,7 @@ done pct exec "$ctid" -- true >/dev/null 2>&1 || die "Le LXC n'est pas joignable apres le demarrage." -printf 'Installation de nginx, git, rsync et des prerequis de build dans le conteneur...\n' -ct_exec "apt-get update && apt-get install -y ca-certificates curl gpg git nginx rsync" - -ct_exec "install -d -m 0755 /opt/chesscubing/repo /opt/chesscubing/ethan-repo /opt/chesscubing/brice-repo /opt/chesscubing/publish /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 fetch origin '$repo_branch' && \ - if git show-ref --verify --quiet 'refs/heads/$repo_branch'; then git checkout '$repo_branch'; else git checkout -b '$repo_branch' --track 'origin/$repo_branch'; fi && \ - git pull --ff-only origin '$repo_branch'; \ -fi" +ct_exec "install -d -m 0755 /opt/chesscubing/repo /opt/chesscubing/ethan-repo /opt/chesscubing/brice-repo /opt/chesscubing/deploy /opt/chesscubing/config" ct_exec "cat > /usr/local/bin/update-chesscubing <<'SCRIPT' #!/usr/bin/env bash @@ -378,30 +394,52 @@ trap 'printf \"Erreur: echec de la commande [%s] a la ligne %s.\\n\" \"\$BASH_CO main_repo_dir='/opt/chesscubing/repo' ethan_repo_dir='/opt/chesscubing/ethan-repo' brice_repo_dir='/opt/chesscubing/brice-repo' -publish_root='/opt/chesscubing/publish' -web_root='/var/www/chesscubing/current' +deploy_dir='/opt/chesscubing/deploy' +config_dir='/opt/chesscubing/config' +env_file=\"\$config_dir/chesscubing.env\" main_branch=\"\${1:-${repo_branch}}\" +public_base_url_override=\"\${2:-}\" +web_port_override=\"\${3:-}\" +keycloak_admin_user_override=\"\${4:-}\" +keycloak_admin_password_override=\"\${5:-}\" +main_repo_url='${repo_url}' ethan_repo_url='${ethan_repo_url}' ethan_branch='${ethan_repo_branch}' brice_repo_url='${brice_repo_url}' brice_branch='${brice_repo_branch}' -ensure_dotnet_sdk() { - if command -v dotnet >/dev/null 2>&1; then - return 0 +random_secret() { + od -An -N24 -tx1 /dev/urandom | tr -d ' \n' +} + +ensure_base_packages() { + apt-get update + apt-get install -y ca-certificates curl gpg git rsync +} + +ensure_docker_stack() { + ensure_base_packages + + if ! command -v docker >/dev/null 2>&1 || ! docker compose version >/dev/null 2>&1; then + install -m 0755 -d /etc/apt/keyrings + + if [[ ! -f /etc/apt/keyrings/docker.asc ]]; then + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc + fi + + if [[ ! -f /etc/apt/sources.list.d/docker.list ]]; then + printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian %s stable\n' \ + \"\$(dpkg --print-architecture)\" \ + \"\$(. /etc/os-release && printf '%s' \"\$VERSION_CODENAME\")\" > /etc/apt/sources.list.d/docker.list + fi + + apt-get update + apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin fi - apt-get update - apt-get install -y ca-certificates curl gpg - - if [[ ! -f /etc/apt/sources.list.d/microsoft-prod.list ]]; then - curl -fsSL https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -o /tmp/packages-microsoft-prod.deb - dpkg -i /tmp/packages-microsoft-prod.deb - rm -f /tmp/packages-microsoft-prod.deb - fi - - apt-get update - apt-get install -y dotnet-sdk-10.0 + systemctl enable docker >/dev/null 2>&1 || true + systemctl restart docker } sync_git_repo() { @@ -437,111 +475,189 @@ sync_git_repo() { git clone --branch \"\$branch\" --single-branch \"\$repo_url\" \"\$repo_dir\" } -publish_blazor_app() { - local repo_dir=\"\$1\" - local output_dir=\"\$2\" +get_env_var() { + local key=\"\$1\" - ensure_dotnet_sdk - rm -rf \"\$output_dir\" - dotnet publish \"\$repo_dir/ChessCubing.App/ChessCubing.App.csproj\" -c Release -o \"\$output_dir\" + [[ -f \"\$env_file\" ]] || return 0 + awk -F= -v wanted=\"\$key\" '\$1 == wanted { print substr(\$0, length(wanted) + 2); exit }' \"\$env_file\" } -publish_static_tree() { - local source_dir=\"\$1\" - local destination_dir=\"\$2\" +set_env_var() { + local key=\"\$1\" + local value=\"\$2\" - install -d -m 0755 \"\$destination_dir\" - - rsync -a --delete \ - --include='*/' \ - --include='*.html' \ - --include='*.css' \ - --include='*.js' \ - --include='*.mjs' \ - --include='*.png' \ - --include='*.jpg' \ - --include='*.jpeg' \ - --include='*.svg' \ - --include='*.webp' \ - --include='*.ico' \ - --include='*.pdf' \ - --include='*.json' \ - --include='*.webmanifest' \ - --exclude='*' \ - \"\$source_dir/\" \"\$destination_dir/\" -} - -ensure_browser_stub() { - local destination_dir=\"\$1\" - local stub_path=\"\$destination_dir/cordova.js\" - - if [[ ! -f \"\$stub_path\" ]]; then - printf '%s\n' '// Browser stub for Cordova builds.' > \"\$stub_path\" + touch \"\$env_file\" + if grep -q \"^\${key}=\" \"\$env_file\" 2>/dev/null; then + sed -i \"s|^\${key}=.*|\${key}=\${value}|\" \"\$env_file\" + else + printf '%s=%s\n' \"\$key\" \"\$value\" >> \"\$env_file\" fi } -sync_git_repo \"\$main_repo_dir\" '${repo_url}' \"\$main_branch\" 'principal' +ensure_env_default() { + local key=\"\$1\" + local fallback=\"\$2\" + local value + + value=\"\$(get_env_var \"\$key\")\" + if [[ -z \"\$value\" ]]; then + value=\"\$fallback\" + fi + + set_env_var \"\$key\" \"\$value\" +} + +normalize_public_base_url() { + local value=\"\$1\" + printf '%s\n' \"\${value%/}\" +} + +build_default_public_base_url() { + local port=\"\$1\" + local detected_ip + + detected_ip=\"\$(hostname -I | awk '{print \$1}')\" + [[ -n \"\$detected_ip\" ]] || return 1 + + if [[ \"\$port\" == \"80\" ]]; then + printf 'http://%s\n' \"\$detected_ip\" + else + printf 'http://%s:%s\n' \"\$detected_ip\" \"\$port\" + fi +} + +configure_env_file() { + local current_value + local effective_web_port + local effective_public_base_url + local effective_keycloak_admin_user + local effective_keycloak_admin_password + + effective_web_port=\"\$web_port_override\" + if [[ -z \"\$effective_web_port\" ]]; then + effective_web_port=\"\$(get_env_var WEB_PORT)\" + fi + if [[ -z \"\$effective_web_port\" ]]; then + effective_web_port='80' + fi + set_env_var WEB_PORT \"\$effective_web_port\" + + effective_public_base_url=\"\$public_base_url_override\" + if [[ -z \"\$effective_public_base_url\" ]]; then + effective_public_base_url=\"\$(get_env_var PUBLIC_BASE_URL)\" + fi + if [[ -z \"\$effective_public_base_url\" ]]; then + effective_public_base_url=\"\$(build_default_public_base_url \"\$effective_web_port\" || true)\" + fi + if [[ -z \"\$effective_public_base_url\" ]]; then + if [[ \"\$effective_web_port\" == \"80\" ]]; then + effective_public_base_url='http://localhost' + else + effective_public_base_url=\"http://localhost:\$effective_web_port\" + fi + fi + set_env_var PUBLIC_BASE_URL \"\$(normalize_public_base_url \"\$effective_public_base_url\")\" + + effective_keycloak_admin_user=\"\$keycloak_admin_user_override\" + if [[ -z \"\$effective_keycloak_admin_user\" ]]; then + effective_keycloak_admin_user=\"\$(get_env_var KEYCLOAK_ADMIN_USER)\" + fi + if [[ -z \"\$effective_keycloak_admin_user\" ]]; then + effective_keycloak_admin_user='admin' + fi + set_env_var KEYCLOAK_ADMIN_USER \"\$effective_keycloak_admin_user\" + + effective_keycloak_admin_password=\"\$keycloak_admin_password_override\" + if [[ -z \"\$effective_keycloak_admin_password\" ]]; then + effective_keycloak_admin_password=\"\$(get_env_var KEYCLOAK_ADMIN_PASSWORD)\" + fi + if [[ -z \"\$effective_keycloak_admin_password\" ]]; then + effective_keycloak_admin_password=\"\$(random_secret)\" + fi + set_env_var KEYCLOAK_ADMIN_PASSWORD \"\$effective_keycloak_admin_password\" + + ensure_env_default KEYCLOAK_DB_NAME keycloak + ensure_env_default KEYCLOAK_DB_USER keycloak + current_value=\"\$(get_env_var KEYCLOAK_DB_PASSWORD)\" + if [[ -z \"\$current_value\" ]]; then + current_value=\"\$(random_secret)\" + fi + set_env_var KEYCLOAK_DB_PASSWORD \"\$current_value\" + + ensure_env_default SITE_DB_NAME chesscubing_site + ensure_env_default SITE_DB_USER chesscubing + current_value=\"\$(get_env_var SITE_DB_PASSWORD)\" + if [[ -z \"\$current_value\" ]]; then + current_value=\"\$(random_secret)\" + fi + set_env_var SITE_DB_PASSWORD \"\$current_value\" + + current_value=\"\$(get_env_var SITE_DB_ROOT_PASSWORD)\" + if [[ -z \"\$current_value\" ]]; then + current_value=\"\$(random_secret)\" + fi + set_env_var SITE_DB_ROOT_PASSWORD \"\$current_value\" +} + +sync_deploy_tree() { + install -d -m 0755 \"\$deploy_dir\" \"\$config_dir\" + + rsync -a --delete \ + --exclude='.git/' \ + --exclude='bin/' \ + --exclude='obj/' \ + --exclude='.env' \ + --exclude='node_modules/' \ + \"\$main_repo_dir/\" \"\$deploy_dir/\" + + if [[ -d \"\$ethan_repo_dir/.git\" ]]; then + rm -rf \"\$deploy_dir/ethan\" + rsync -a --delete \ + --exclude='.git/' \ + --exclude='node_modules/' \ + \"\$ethan_repo_dir/\" \"\$deploy_dir/ethan/\" + fi + + if [[ -d \"\$brice_repo_dir/.git\" ]]; then + rm -rf \"\$deploy_dir/brice\" + rsync -a --delete \ + --exclude='.git/' \ + --exclude='node_modules/' \ + \"\$brice_repo_dir/\" \"\$deploy_dir/brice/\" + fi +} + +disable_legacy_nginx() { + systemctl disable --now nginx >/dev/null 2>&1 || true +} + +deploy_stack() { + cp \"\$env_file\" \"\$deploy_dir/.env\" + + cd \"\$deploy_dir\" + docker compose down || true + docker compose up -d --build + docker compose ps +} + +ensure_docker_stack +sync_git_repo \"\$main_repo_dir\" \"\$main_repo_url\" \"\$main_branch\" 'principal' sync_git_repo \"\$ethan_repo_dir\" \"\$ethan_repo_url\" \"\$ethan_branch\" 'Ethan' sync_git_repo \"\$brice_repo_dir\" \"\$brice_repo_url\" \"\$brice_branch\" 'Brice' - -install -d -m 0755 \"\$web_root\" \"\$publish_root\" - -publish_blazor_app \"\$main_repo_dir\" \"\$publish_root/main\" -rsync -a --delete \"\$publish_root/main/wwwroot/\" \"\$web_root/\" -publish_static_tree \"\$ethan_repo_dir\" \"\$web_root/ethan\" -publish_static_tree \"\$brice_repo_dir/www\" \"\$web_root/brice\" -ensure_browser_stub \"\$web_root/brice\" - -chown -R www-data:www-data \"\$web_root\" - -nginx -t -systemctl reload nginx +sync_deploy_tree +configure_env_file +disable_legacy_nginx +deploy_stack 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 = /ethan { - return 301 \$scheme://\$http_host/ethan/; - } - - location /ethan/ { - try_files \$uri \$uri/ /ethan/index.html; - } - - location = /brice { - return 301 \$scheme://\$http_host/brice/; - } - - location /brice/ { - try_files \$uri \$uri/ /brice/index.html; - } - - location / { - try_files \$uri \$uri/ /index.html; - } - - location ~* \.(?:css|js|json|mjs|png|jpg|jpeg|svg|webp|ico|pdf|webmanifest)$ { - expires -1; - add_header Cache-Control 'no-cache, no-store, must-revalidate'; - } -} -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" +printf 'Deploiement de la stack Docker complete dans le LXC...\n' +ct_exec "/usr/local/bin/update-chesscubing '$repo_branch' '$public_base_url' '$web_port' '$keycloak_admin_user' '$keycloak_admin_password'" container_ip="$(pct exec "$ctid" -- bash -lc "hostname -I | awk '{print \$1}'" 2>/dev/null | tr -d '\r' || true)" +public_url="$(pct exec "$ctid" -- bash -lc "awk -F= '/^PUBLIC_BASE_URL=/{print substr(\$0, 17); exit}' /opt/chesscubing/config/chesscubing.env" 2>/dev/null | tr -d '\r' || true)" +effective_keycloak_admin_user="$(pct exec "$ctid" -- bash -lc "awk -F= '/^KEYCLOAK_ADMIN_USER=/{print substr(\$0, 21); exit}' /opt/chesscubing/config/chesscubing.env" 2>/dev/null | tr -d '\r' || true)" +effective_keycloak_admin_password="$(pct exec "$ctid" -- bash -lc "awk -F= '/^KEYCLOAK_ADMIN_PASSWORD=/{print substr(\$0, 25); exit}' /opt/chesscubing/config/chesscubing.env" 2>/dev/null | tr -d '\r' || true)" cat <} +- IP detectee du LXC: ${container_ip:-} +- URL publique configuree: ${public_url:-http://${container_ip:-}} +- Admin Keycloak: ${effective_keycloak_admin_user:-admin} +- Mot de passe admin Keycloak: ${effective_keycloak_admin_password:-} Pour mettre a jour l'application plus tard: ./scripts/update-proxmox-lxc.sh --proxmox-host --proxmox-user --proxmox-password '' --ctid $ctid @@ -576,7 +695,11 @@ if [[ "$LOCAL_MODE" == "1" ]]; then "$ETHAN_REPO_BRANCH" \ "$BRICE_REPO_URL" \ "$BRICE_REPO_BRANCH" \ - "$LXC_PASSWORD" + "$LXC_PASSWORD" \ + "$PUBLIC_BASE_URL" \ + "$WEB_PORT" \ + "$KEYCLOAK_ADMIN_USER" \ + "$KEYCLOAK_ADMIN_PASSWORD" exit 0 fi @@ -618,4 +741,8 @@ sshpass -p "$PROXMOX_PASSWORD" \ "$ETHAN_REPO_BRANCH" \ "$BRICE_REPO_URL" \ "$BRICE_REPO_BRANCH" \ - "$LXC_PASSWORD" < "$payload_script" + "$LXC_PASSWORD" \ + "$PUBLIC_BASE_URL" \ + "$WEB_PORT" \ + "$KEYCLOAK_ADMIN_USER" \ + "$KEYCLOAK_ADMIN_PASSWORD" < "$payload_script" diff --git a/scripts/update-proxmox-lxc.sh b/scripts/update-proxmox-lxc.sh index 243745e..7f844a4 100755 --- a/scripts/update-proxmox-lxc.sh +++ b/scripts/update-proxmox-lxc.sh @@ -22,11 +22,17 @@ Options principales: --local Execute directement sur l'hote Proxmox local --ctid CTID du LXC a mettre a jour --hostname Nom du LXC si le CTID n'est pas fourni (defaut: chesscubing-web) + --repo-url Depot Git principal a deployer --branch Branche Git a deployer (defaut: main) --ethan-repo-url Depot Git de l'application Ethan --ethan-branch Branche Git de l'application Ethan (defaut: main) --brice-repo-url Depot Git de l'application Brice --brice-branch Branche Git de l'application Brice (defaut: main) + --public-base-url URL publique du site (ex: http://jeu.example.com) + --web-port Port HTTP expose dans le LXC (defaut: conserve ou 80) + --keycloak-admin-user Utilisateur admin Keycloak a forcer + --keycloak-admin-password + Mot de passe admin Keycloak a forcer -h, --help Affiche cette aide EOF } @@ -48,11 +54,16 @@ LOCAL_MODE="0" CTID="" LXC_HOSTNAME="chesscubing-web" +REPO_URL="https://git.jeannerot.fr/christophe/chesscubing.git" REPO_BRANCH="main" ETHAN_REPO_URL="https://git.jeannerot.fr/Mineloulou/Chesscubing.git" ETHAN_REPO_BRANCH="main" BRICE_REPO_URL="https://git.jeannerot.fr/Lescratcheur/ChessCubing.git" BRICE_REPO_BRANCH="main" +PUBLIC_BASE_URL="" +WEB_PORT="" +KEYCLOAK_ADMIN_USER="" +KEYCLOAK_ADMIN_PASSWORD="" while [[ $# -gt 0 ]]; do case "$1" in @@ -84,6 +95,10 @@ while [[ $# -gt 0 ]]; do LXC_HOSTNAME="${2:-}" shift 2 ;; + --repo-url) + REPO_URL="${2:-}" + shift 2 + ;; --branch) REPO_BRANCH="${2:-}" shift 2 @@ -104,6 +119,22 @@ while [[ $# -gt 0 ]]; do BRICE_REPO_BRANCH="${2:-}" shift 2 ;; + --public-base-url) + PUBLIC_BASE_URL="${2:-}" + shift 2 + ;; + --web-port) + WEB_PORT="${2:-}" + shift 2 + ;; + --keycloak-admin-user) + KEYCLOAK_ADMIN_USER="${2:-}" + shift 2 + ;; + --keycloak-admin-password) + KEYCLOAK_ADMIN_PASSWORD="${2:-}" + shift 2 + ;; -h | --help) usage exit 0 @@ -133,11 +164,16 @@ trap 'printf "Erreur: echec de la commande [%s] a la ligne %s.\n" "$BASH_COMMAND ctid="$1" lxc_hostname="$2" -repo_branch="$3" -ethan_repo_url="$4" -ethan_repo_branch="$5" -brice_repo_url="$6" -brice_repo_branch="$7" +repo_url="$3" +repo_branch="$4" +ethan_repo_url="$5" +ethan_repo_branch="$6" +brice_repo_url="$7" +brice_repo_branch="$8" +public_base_url="$9" +web_port="${10}" +keycloak_admin_user="${11}" +keycloak_admin_password="${12}" die() { printf 'Erreur: %s\n' "$*" >&2 @@ -178,15 +214,28 @@ if [[ -n "$detected_hostname" ]]; then lxc_hostname="$detected_hostname" fi -if ! pct status "$ctid" | grep -q "running"; then - pct start "$ctid" - sleep 5 +if pct status "$ctid" | grep -q "running"; then + pct stop "$ctid" fi +pct set "$ctid" --features nesting=1,keyctl=1 >/dev/null +pct start "$ctid" + +for _ in $(seq 1 20); do + if pct exec "$ctid" -- true >/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 redemarrage." + ct_exec() { pct exec "$ctid" -- bash -lc "$1" } +ct_exec "install -d -m 0755 /opt/chesscubing/repo /opt/chesscubing/ethan-repo /opt/chesscubing/brice-repo /opt/chesscubing/deploy /opt/chesscubing/config" + ct_exec "cat > /usr/local/bin/update-chesscubing <<'SCRIPT' #!/usr/bin/env bash set -Eeuo pipefail @@ -196,30 +245,52 @@ trap 'printf \"Erreur: echec de la commande [%s] a la ligne %s.\\n\" \"\$BASH_CO main_repo_dir='/opt/chesscubing/repo' ethan_repo_dir='/opt/chesscubing/ethan-repo' brice_repo_dir='/opt/chesscubing/brice-repo' -publish_root='/opt/chesscubing/publish' -web_root='/var/www/chesscubing/current' +deploy_dir='/opt/chesscubing/deploy' +config_dir='/opt/chesscubing/config' +env_file=\"\$config_dir/chesscubing.env\" main_branch=\"\${1:-${repo_branch}}\" +public_base_url_override=\"\${2:-}\" +web_port_override=\"\${3:-}\" +keycloak_admin_user_override=\"\${4:-}\" +keycloak_admin_password_override=\"\${5:-}\" +main_repo_url='${repo_url}' ethan_repo_url='${ethan_repo_url}' ethan_branch='${ethan_repo_branch}' brice_repo_url='${brice_repo_url}' brice_branch='${brice_repo_branch}' -ensure_dotnet_sdk() { - if command -v dotnet >/dev/null 2>&1; then - return 0 +random_secret() { + od -An -N24 -tx1 /dev/urandom | tr -d ' \n' +} + +ensure_base_packages() { + apt-get update + apt-get install -y ca-certificates curl gpg git rsync +} + +ensure_docker_stack() { + ensure_base_packages + + if ! command -v docker >/dev/null 2>&1 || ! docker compose version >/dev/null 2>&1; then + install -m 0755 -d /etc/apt/keyrings + + if [[ ! -f /etc/apt/keyrings/docker.asc ]]; then + curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc + chmod a+r /etc/apt/keyrings/docker.asc + fi + + if [[ ! -f /etc/apt/sources.list.d/docker.list ]]; then + printf 'deb [arch=%s signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian %s stable\n' \ + \"\$(dpkg --print-architecture)\" \ + \"\$(. /etc/os-release && printf '%s' \"\$VERSION_CODENAME\")\" > /etc/apt/sources.list.d/docker.list + fi + + apt-get update + apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin fi - apt-get update - apt-get install -y ca-certificates curl gpg - - if [[ ! -f /etc/apt/sources.list.d/microsoft-prod.list ]]; then - curl -fsSL https://packages.microsoft.com/config/debian/12/packages-microsoft-prod.deb -o /tmp/packages-microsoft-prod.deb - dpkg -i /tmp/packages-microsoft-prod.deb - rm -f /tmp/packages-microsoft-prod.deb - fi - - apt-get update - apt-get install -y dotnet-sdk-10.0 + systemctl enable docker >/dev/null 2>&1 || true + systemctl restart docker } sync_git_repo() { @@ -255,116 +326,194 @@ sync_git_repo() { git clone --branch \"\$branch\" --single-branch \"\$repo_url\" \"\$repo_dir\" } -publish_blazor_app() { - local repo_dir=\"\$1\" - local output_dir=\"\$2\" +get_env_var() { + local key=\"\$1\" - ensure_dotnet_sdk - rm -rf \"\$output_dir\" - dotnet publish \"\$repo_dir/ChessCubing.App/ChessCubing.App.csproj\" -c Release -o \"\$output_dir\" + [[ -f \"\$env_file\" ]] || return 0 + awk -F= -v wanted=\"\$key\" '\$1 == wanted { print substr(\$0, length(wanted) + 2); exit }' \"\$env_file\" } -publish_static_tree() { - local source_dir=\"\$1\" - local destination_dir=\"\$2\" +set_env_var() { + local key=\"\$1\" + local value=\"\$2\" - install -d -m 0755 \"\$destination_dir\" - - rsync -a --delete \ - --include='*/' \ - --include='*.html' \ - --include='*.css' \ - --include='*.js' \ - --include='*.mjs' \ - --include='*.png' \ - --include='*.jpg' \ - --include='*.jpeg' \ - --include='*.svg' \ - --include='*.webp' \ - --include='*.ico' \ - --include='*.pdf' \ - --include='*.json' \ - --include='*.webmanifest' \ - --exclude='*' \ - \"\$source_dir/\" \"\$destination_dir/\" -} - -ensure_browser_stub() { - local destination_dir=\"\$1\" - local stub_path=\"\$destination_dir/cordova.js\" - - if [[ ! -f \"\$stub_path\" ]]; then - printf '%s\n' '// Browser stub for Cordova builds.' > \"\$stub_path\" + touch \"\$env_file\" + if grep -q \"^\${key}=\" \"\$env_file\" 2>/dev/null; then + sed -i \"s|^\${key}=.*|\${key}=\${value}|\" \"\$env_file\" + else + printf '%s=%s\n' \"\$key\" \"\$value\" >> \"\$env_file\" fi } -sync_git_repo \"\$main_repo_dir\" '' \"\$main_branch\" 'principal' +ensure_env_default() { + local key=\"\$1\" + local fallback=\"\$2\" + local value + + value=\"\$(get_env_var \"\$key\")\" + if [[ -z \"\$value\" ]]; then + value=\"\$fallback\" + fi + + set_env_var \"\$key\" \"\$value\" +} + +normalize_public_base_url() { + local value=\"\$1\" + printf '%s\n' \"\${value%/}\" +} + +build_default_public_base_url() { + local port=\"\$1\" + local detected_ip + + detected_ip=\"\$(hostname -I | awk '{print \$1}')\" + [[ -n \"\$detected_ip\" ]] || return 1 + + if [[ \"\$port\" == \"80\" ]]; then + printf 'http://%s\n' \"\$detected_ip\" + else + printf 'http://%s:%s\n' \"\$detected_ip\" \"\$port\" + fi +} + +configure_env_file() { + local current_value + local effective_web_port + local effective_public_base_url + local effective_keycloak_admin_user + local effective_keycloak_admin_password + + effective_web_port=\"\$web_port_override\" + if [[ -z \"\$effective_web_port\" ]]; then + effective_web_port=\"\$(get_env_var WEB_PORT)\" + fi + if [[ -z \"\$effective_web_port\" ]]; then + effective_web_port='80' + fi + set_env_var WEB_PORT \"\$effective_web_port\" + + effective_public_base_url=\"\$public_base_url_override\" + if [[ -z \"\$effective_public_base_url\" ]]; then + effective_public_base_url=\"\$(get_env_var PUBLIC_BASE_URL)\" + fi + if [[ -z \"\$effective_public_base_url\" ]]; then + effective_public_base_url=\"\$(build_default_public_base_url \"\$effective_web_port\" || true)\" + fi + if [[ -z \"\$effective_public_base_url\" ]]; then + if [[ \"\$effective_web_port\" == \"80\" ]]; then + effective_public_base_url='http://localhost' + else + effective_public_base_url=\"http://localhost:\$effective_web_port\" + fi + fi + set_env_var PUBLIC_BASE_URL \"\$(normalize_public_base_url \"\$effective_public_base_url\")\" + + effective_keycloak_admin_user=\"\$keycloak_admin_user_override\" + if [[ -z \"\$effective_keycloak_admin_user\" ]]; then + effective_keycloak_admin_user=\"\$(get_env_var KEYCLOAK_ADMIN_USER)\" + fi + if [[ -z \"\$effective_keycloak_admin_user\" ]]; then + effective_keycloak_admin_user='admin' + fi + set_env_var KEYCLOAK_ADMIN_USER \"\$effective_keycloak_admin_user\" + + effective_keycloak_admin_password=\"\$keycloak_admin_password_override\" + if [[ -z \"\$effective_keycloak_admin_password\" ]]; then + effective_keycloak_admin_password=\"\$(get_env_var KEYCLOAK_ADMIN_PASSWORD)\" + fi + if [[ -z \"\$effective_keycloak_admin_password\" ]]; then + effective_keycloak_admin_password=\"\$(random_secret)\" + fi + set_env_var KEYCLOAK_ADMIN_PASSWORD \"\$effective_keycloak_admin_password\" + + ensure_env_default KEYCLOAK_DB_NAME keycloak + ensure_env_default KEYCLOAK_DB_USER keycloak + current_value=\"\$(get_env_var KEYCLOAK_DB_PASSWORD)\" + if [[ -z \"\$current_value\" ]]; then + current_value=\"\$(random_secret)\" + fi + set_env_var KEYCLOAK_DB_PASSWORD \"\$current_value\" + + ensure_env_default SITE_DB_NAME chesscubing_site + ensure_env_default SITE_DB_USER chesscubing + current_value=\"\$(get_env_var SITE_DB_PASSWORD)\" + if [[ -z \"\$current_value\" ]]; then + current_value=\"\$(random_secret)\" + fi + set_env_var SITE_DB_PASSWORD \"\$current_value\" + + current_value=\"\$(get_env_var SITE_DB_ROOT_PASSWORD)\" + if [[ -z \"\$current_value\" ]]; then + current_value=\"\$(random_secret)\" + fi + set_env_var SITE_DB_ROOT_PASSWORD \"\$current_value\" +} + +sync_deploy_tree() { + install -d -m 0755 \"\$deploy_dir\" \"\$config_dir\" + + rsync -a --delete \ + --exclude='.git/' \ + --exclude='bin/' \ + --exclude='obj/' \ + --exclude='.env' \ + --exclude='node_modules/' \ + \"\$main_repo_dir/\" \"\$deploy_dir/\" + + if [[ -d \"\$ethan_repo_dir/.git\" ]]; then + rm -rf \"\$deploy_dir/ethan\" + rsync -a --delete \ + --exclude='.git/' \ + --exclude='node_modules/' \ + \"\$ethan_repo_dir/\" \"\$deploy_dir/ethan/\" + fi + + if [[ -d \"\$brice_repo_dir/.git\" ]]; then + rm -rf \"\$deploy_dir/brice\" + rsync -a --delete \ + --exclude='.git/' \ + --exclude='node_modules/' \ + \"\$brice_repo_dir/\" \"\$deploy_dir/brice/\" + fi +} + +disable_legacy_nginx() { + systemctl disable --now nginx >/dev/null 2>&1 || true +} + +deploy_stack() { + cp \"\$env_file\" \"\$deploy_dir/.env\" + + cd \"\$deploy_dir\" + docker compose down || true + docker compose up -d --build + docker compose ps +} + +ensure_docker_stack +sync_git_repo \"\$main_repo_dir\" \"\$main_repo_url\" \"\$main_branch\" 'principal' sync_git_repo \"\$ethan_repo_dir\" \"\$ethan_repo_url\" \"\$ethan_branch\" 'Ethan' sync_git_repo \"\$brice_repo_dir\" \"\$brice_repo_url\" \"\$brice_branch\" 'Brice' - -install -d -m 0755 \"\$web_root\" \"\$publish_root\" - -publish_blazor_app \"\$main_repo_dir\" \"\$publish_root/main\" -rsync -a --delete \"\$publish_root/main/wwwroot/\" \"\$web_root/\" -publish_static_tree \"\$ethan_repo_dir\" \"\$web_root/ethan\" -publish_static_tree \"\$brice_repo_dir/www\" \"\$web_root/brice\" -ensure_browser_stub \"\$web_root/brice\" - -chown -R www-data:www-data \"\$web_root\" - -nginx -t -systemctl reload nginx +sync_deploy_tree +configure_env_file +disable_legacy_nginx +deploy_stack 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 = /ethan { - return 301 \$scheme://\$http_host/ethan/; - } - - location /ethan/ { - try_files \$uri \$uri/ /ethan/index.html; - } - - location = /brice { - return 301 \$scheme://\$http_host/brice/; - } - - location /brice/ { - try_files \$uri \$uri/ /brice/index.html; - } - - location / { - try_files \$uri \$uri/ /index.html; - } - - location ~* \.(?:css|js|json|mjs|png|jpg|jpeg|svg|webp|ico|pdf|webmanifest)$ { - expires -1; - add_header Cache-Control 'no-cache, no-store, must-revalidate'; - } -} -NGINX -rm -f /etc/nginx/sites-enabled/default -ln -sf /etc/nginx/sites-available/chesscubing.conf /etc/nginx/sites-enabled/chesscubing.conf" - -pct exec "$ctid" -- /usr/local/bin/update-chesscubing "$repo_branch" +ct_exec "/usr/local/bin/update-chesscubing '$repo_branch' '$public_base_url' '$web_port' '$keycloak_admin_user' '$keycloak_admin_password'" container_ip="$(pct exec "$ctid" -- bash -lc "hostname -I | awk '{print \$1}'" 2>/dev/null | tr -d '\r' || true)" +public_url="$(pct exec "$ctid" -- bash -lc "awk -F= '/^PUBLIC_BASE_URL=/{print substr(\$0, 17); exit}' /opt/chesscubing/config/chesscubing.env" 2>/dev/null | tr -d '\r' || true)" cat <} +- IP detectee du LXC: ${container_ip:-} +- URL publique configuree: ${public_url:-http://${container_ip:-}} EOF REMOTE @@ -373,11 +522,16 @@ if [[ "$LOCAL_MODE" == "1" ]]; then bash "$payload_script" \ "$CTID" \ "$LXC_HOSTNAME" \ + "$REPO_URL" \ "$REPO_BRANCH" \ "$ETHAN_REPO_URL" \ "$ETHAN_REPO_BRANCH" \ "$BRICE_REPO_URL" \ - "$BRICE_REPO_BRANCH" + "$BRICE_REPO_BRANCH" \ + "$PUBLIC_BASE_URL" \ + "$WEB_PORT" \ + "$KEYCLOAK_ADMIN_USER" \ + "$KEYCLOAK_ADMIN_PASSWORD" exit 0 fi @@ -404,8 +558,13 @@ sshpass -p "$PROXMOX_PASSWORD" \ bash -s -- \ "$CTID" \ "$LXC_HOSTNAME" \ + "$REPO_URL" \ "$REPO_BRANCH" \ "$ETHAN_REPO_URL" \ "$ETHAN_REPO_BRANCH" \ "$BRICE_REPO_URL" \ - "$BRICE_REPO_BRANCH" < "$payload_script" + "$BRICE_REPO_BRANCH" \ + "$PUBLIC_BASE_URL" \ + "$WEB_PORT" \ + "$KEYCLOAK_ADMIN_USER" \ + "$KEYCLOAK_ADMIN_PASSWORD" < "$payload_script"