diff --git a/README.md b/README.md index cb80632..f44e90b 100644 --- a/README.md +++ b/README.md @@ -40,6 +40,12 @@ Tous les scripts sont invoqués depuis la **racine de ia_dev** (ce dépôt). Les scripts dans `deploy/` **déploient et versionnent les projets configurés** (lecoffreio, enso, algo, etc.) dans leurs répertoires — ils ne déploient pas ia_dev. À lancer depuis la racine de ia_dev. Chaque script accepte en option un **project_id** en premier argument (ou `--project ` pour pousse.sh) pour cibler le projet ; sinon le projet est résolu par MAIL_TO ou AI_AGENT_TOKEN. Les chemins absolus dans `projects//conf.json` indiquent où se trouve chaque projet et ses scripts de déploiement. +**Orchestration générique (métier dans le dépôt cible)** : + +- **deploy.sh** : point d’entrée `./deploy/deploy.sh [options…]` — exporte `IA_PROJECT_ID`, puis exécute **orchestrator.sh** (même contrat que les scripts ci-dessous qui invoquent l’orchestrateur lorsque `IA_PROJECT_ID` est défini). +- **orchestrator.sh** : lit `deploy.hooks.phases` dans `conf.json` (chemins relatifs à `repository_root`) ; si `phases` est vide, exécute `deploy.deploy_script_path`. Le métier (Prisma, systemd, build distant, etc.) reste dans les scripts du projet. +- **run-project-hooks.sh** : délègue à **orchestrator.sh** (alias pour compatibilité avec les anciennes invocations). + - **bump-version.sh** : lit `projects//conf.json` (version.package_json_paths, version.splash_app_name) et met à jour VERSION + package.json du **projet configuré**. Invocation : `./deploy/bump-version.sh [project_id] [message]` depuis la racine de ia_dev. - **deploy-by-script-to.sh** : checkout branche cible (pprod|prod), sync origin, exécute `deploy/scripts_v2/deploy.sh` du **projet configuré** (chemin dans conf), puis revient sur test. Invocation : `./deploy/deploy-by-script-to.sh [project_id] `. - **pousse.sh** : build check, commit, push. Invocation : `./deploy/pousse.sh [project_id|--project ] [--remote ] [--bump-version]` avec le message de commit sur STDIN. diff --git a/deploy/change-to-all-branches.sh b/deploy/change-to-all-branches.sh index 15a728e..f37470c 100755 --- a/deploy/change-to-all-branches.sh +++ b/deploy/change-to-all-branches.sh @@ -47,8 +47,8 @@ echo "[change-to-all-branches] Aligning branches..." # scripts_v2 lives in the host project's deploy/ (not necessarily under ia_dev) DEPLOY_SCRIPTS_V2="${PROJECT_ROOT}/deploy/scripts_v2" echo "[change-to-all-branches] Deploying test (--import-v1 --skipSetupHost, --no-sync-origin because we just pushed)..." -if [[ -n "${IA_PROJECT_ID:-}" && -x "${DEPLOY_DIR}/run-project-hooks.sh" ]]; then - "${DEPLOY_DIR}/run-project-hooks.sh" test --import-v1 --skipSetupHost --no-sync-origin +if [[ -n "${IA_PROJECT_ID:-}" && -x "${DEPLOY_DIR}/orchestrator.sh" ]]; then + "${DEPLOY_DIR}/orchestrator.sh" test --import-v1 --skipSetupHost --no-sync-origin else "${DEPLOY_SCRIPTS_V2}/deploy.sh" test --import-v1 --skipSetupHost --no-sync-origin fi diff --git a/deploy/deploy-by-script-to.sh b/deploy/deploy-by-script-to.sh index c7d76e1..feb85d9 100755 --- a/deploy/deploy-by-script-to.sh +++ b/deploy/deploy-by-script-to.sh @@ -74,8 +74,8 @@ git fetch origin git reset --hard "origin/${TARGET_BRANCH}" echo "[deploy-by-script-to] Step 4/5: deploy ${TARGET_BRANCH} (--import-v1 --skipSetupHost)..." -if [[ -n "${IA_PROJECT_ID:-}" && -x "${DEPLOY_IA}/run-project-hooks.sh" ]]; then - "${DEPLOY_IA}/run-project-hooks.sh" "$TARGET_BRANCH" --import-v1 --skipSetupHost +if [[ -n "${IA_PROJECT_ID:-}" && -x "${DEPLOY_IA}/orchestrator.sh" ]]; then + "${DEPLOY_IA}/orchestrator.sh" "$TARGET_BRANCH" --import-v1 --skipSetupHost else deploy_script="$PROJECT_ROOT/deploy/scripts_v2/deploy.sh" if [[ -n "${PROJECT_CONFIG_PATH:-}" && -f "${PROJECT_CONFIG_PATH:-}" ]] && command -v jq >/dev/null 2>&1; then diff --git a/deploy/deploy.sh b/deploy/deploy.sh new file mode 100755 index 0000000..e162948 --- /dev/null +++ b/deploy/deploy.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash +# Generic deploy entry from ia_dev: sets IA_PROJECT_ID and runs the orchestrator. +# Business logic remains in the target repository (deploy_script_path, hooks.phases). +# Usage (from ia_dev root): ./deploy/deploy.sh [options...] +# Example: ./deploy/deploy.sh lecoffreio test --import-v1 --skipSetupHost +set -euo pipefail + +SCRIPT_REAL="$(readlink -f "${BASH_SOURCE[0]:-$0}" 2>/dev/null || realpath "${BASH_SOURCE[0]:-$0}" 2>/dev/null || echo "${BASH_SOURCE[0]:-$0}")" +DEPLOY_DIR="$(cd "$(dirname "$SCRIPT_REAL")" && pwd)" +IA_DEV_ROOT="$(cd "$DEPLOY_DIR/.." && pwd)" + +if [[ $# -lt 2 ]]; then + echo "[deploy][ERROR] Missing arguments" >&2 + echo "Usage: $0 [options passed to each phase / fallback deploy script]" >&2 + echo "Example: $0 lecoffreio test --import-v1 --skipSetupHost" >&2 + exit 1 +fi + +CONF="${IA_DEV_ROOT}/projects/${1}/conf.json" +if [[ ! -f "$CONF" ]]; then + echo "[deploy][ERROR] No conf for project '${1}': ${CONF}" >&2 + exit 1 +fi + +export IA_PROJECT_ID="$1" +shift +exec "$DEPLOY_DIR/orchestrator.sh" "$@" diff --git a/deploy/lib/README.md b/deploy/lib/README.md index b384671..c03ce82 100644 --- a/deploy/lib/README.md +++ b/deploy/lib/README.md @@ -12,3 +12,8 @@ Optional `deploy_script_tee_log_if_requested ` — re ## Policy Project-specific logic (Prisma, systemd unit names, remote app layout, LeCoffre domains) stays under each repository’s `deploy/scripts_v2/`. Only transport/logging helpers live here. + +## Orchestration (`../orchestrator.sh`, `../deploy.sh`) + +- **`deploy.sh`** : `./deploy/deploy.sh [args]` — generic entry; business scripts live in the target repo (`deploy_script_path` / `hooks.phases`). +- **`orchestrator.sh`** : runs phases or fallback script; **`run-project-hooks.sh`** execs it for backward compatibility. diff --git a/deploy/orchestrator.sh b/deploy/orchestrator.sh new file mode 100755 index 0000000..a1dbe64 --- /dev/null +++ b/deploy/orchestrator.sh @@ -0,0 +1,74 @@ +#!/usr/bin/env bash +# Generic deploy orchestrator: runs deploy.hooks.phases from projects//conf.json (paths relative to repository_root). +# If phases is empty or missing, exec deploy.deploy_script_path with the same arguments. +# Business logic (Prisma, systemd, remote layout) stays in each project's scripts under repository_root. +# Usage: orchestrator.sh [options passed to each phase / fallback script] +# Requires: IA_PROJECT_ID, IA_DEV_ROOT (or re-exec from project root like change-to-all-branches). +set -euo pipefail + +SCRIPT_REAL="$(readlink -f "${BASH_SOURCE[0]:-$0}" 2>/dev/null || realpath "${BASH_SOURCE[0]:-$0}" 2>/dev/null || echo "${BASH_SOURCE[0]:-$0}")" +DEPLOY_DIR="$(cd "$(dirname "$SCRIPT_REAL")" && pwd)" +IA_DEV_ROOT="$(cd "$DEPLOY_DIR/.." && pwd)" + +_ORCH_TAG="[orchestrator]" + +if [[ -z "${IA_PROJECT_ID:-}" ]]; then + echo "${_ORCH_TAG}[ERROR] IA_PROJECT_ID is not set" >&2 + exit 1 +fi + +# shellcheck source=../lib/project_config.sh +source "${IA_DEV_ROOT}/lib/project_config.sh" +# shellcheck source=../lib/project_git_root_from_conf.sh +source "${IA_DEV_ROOT}/lib/project_git_root_from_conf.sh" +ia_dev_resolve_project_git_root +REPO_ROOT="${IA_PROJECT_GIT_ROOT:-}" +if [[ -z "$REPO_ROOT" || ! -d "$REPO_ROOT" ]]; then + echo "${_ORCH_TAG}[ERROR] Could not resolve repository root for project ${IA_PROJECT_ID}" >&2 + exit 1 +fi + +CONF="${PROJECT_CONFIG_PATH:-}" +if [[ -z "$CONF" || ! -f "$CONF" ]]; then + echo "${_ORCH_TAG}[ERROR] Missing conf: ${CONF:-}" >&2 + exit 1 +fi + +if ! command -v jq >/dev/null 2>&1; then + echo "${_ORCH_TAG}[ERROR] jq is required to read deploy.hooks.phases" >&2 + exit 1 +fi + +SECRETS_PATH_CFG="$(jq -r '.deploy.secrets_path // empty' "$CONF")" +if [[ -n "$SECRETS_PATH_CFG" && "$SECRETS_PATH_CFG" != "null" && -d "$SECRETS_PATH_CFG" ]]; then + export SECRETS_BASE="$SECRETS_PATH_CFG" + export LECOFFRE_SECRETS_BASE="$SECRETS_PATH_CFG" +fi + +DEPLOY_SCRIPT_PATH="$(jq -r '.deploy.deploy_script_path // empty' "$CONF")" +if [[ -z "$DEPLOY_SCRIPT_PATH" || ! -f "$DEPLOY_SCRIPT_PATH" ]]; then + echo "${_ORCH_TAG}[ERROR] deploy.deploy_script_path missing or not a file: ${DEPLOY_SCRIPT_PATH:-}" >&2 + exit 1 +fi + +PHASE_COUNT="$(jq '.deploy.hooks.phases // [] | length' "$CONF")" +if [[ "$PHASE_COUNT" == "0" ]]; then + exec bash "$DEPLOY_SCRIPT_PATH" "$@" +fi + +mapfile -t PHASE_SCRIPTS < <(jq -r '.deploy.hooks.phases[]? | if type == "string" then . elif type == "object" and (.run | type == "string") then .run else empty end' "$CONF") + +if [[ ${#PHASE_SCRIPTS[@]} -eq 0 ]]; then + exec bash "$DEPLOY_SCRIPT_PATH" "$@" +fi + +for rel in "${PHASE_SCRIPTS[@]}"; do + [[ -z "$rel" ]] && continue + phase_path="${REPO_ROOT}/${rel}" + if [[ ! -f "$phase_path" ]]; then + echo "${_ORCH_TAG}[ERROR] Phase script not found: ${phase_path}" >&2 + exit 1 + fi + echo "${_ORCH_TAG} Running: ${rel} $*" + bash "$phase_path" "$@" +done diff --git a/deploy/run-project-hooks.sh b/deploy/run-project-hooks.sh index 7eff94c..65c199b 100755 --- a/deploy/run-project-hooks.sh +++ b/deploy/run-project-hooks.sh @@ -1,71 +1,9 @@ #!/usr/bin/env bash -# Run deploy.hooks.phases from projects//conf.json (paths relative to repository_root). -# If phases is empty or missing, exec deploy.deploy_script_path with the same arguments. +# Backward-compatible alias: delegates to orchestrator.sh (generic deploy orchestrator). # Usage: run-project-hooks.sh [options passed to each phase / fallback script] -# Requires: IA_PROJECT_ID, IA_DEV_ROOT (or re-exec from project root like change-to-all-branches). +# Requires: IA_PROJECT_ID (set by caller or by deploy.sh). set -euo pipefail SCRIPT_REAL="$(readlink -f "${BASH_SOURCE[0]:-$0}" 2>/dev/null || realpath "${BASH_SOURCE[0]:-$0}" 2>/dev/null || echo "${BASH_SOURCE[0]:-$0}")" DEPLOY_DIR="$(cd "$(dirname "$SCRIPT_REAL")" && pwd)" -IA_DEV_ROOT="$(cd "$DEPLOY_DIR/.." && pwd)" - -if [[ -z "${IA_PROJECT_ID:-}" ]]; then - echo "[run-project-hooks][ERROR] IA_PROJECT_ID is not set" >&2 - exit 1 -fi - -# shellcheck source=../lib/project_config.sh -source "${IA_DEV_ROOT}/lib/project_config.sh" -# shellcheck source=../lib/project_git_root_from_conf.sh -source "${IA_DEV_ROOT}/lib/project_git_root_from_conf.sh" -ia_dev_resolve_project_git_root -REPO_ROOT="${IA_PROJECT_GIT_ROOT:-}" -if [[ -z "$REPO_ROOT" || ! -d "$REPO_ROOT" ]]; then - echo "[run-project-hooks][ERROR] Could not resolve repository root for project ${IA_PROJECT_ID}" >&2 - exit 1 -fi - -CONF="${PROJECT_CONFIG_PATH:-}" -if [[ -z "$CONF" || ! -f "$CONF" ]]; then - echo "[run-project-hooks][ERROR] Missing conf: ${CONF:-}" >&2 - exit 1 -fi - -if ! command -v jq >/dev/null 2>&1; then - echo "[run-project-hooks][ERROR] jq is required to read deploy.hooks.phases" >&2 - exit 1 -fi - -SECRETS_PATH_CFG="$(jq -r '.deploy.secrets_path // empty' "$CONF")" -if [[ -n "$SECRETS_PATH_CFG" && "$SECRETS_PATH_CFG" != "null" && -d "$SECRETS_PATH_CFG" ]]; then - export SECRETS_BASE="$SECRETS_PATH_CFG" - export LECOFFRE_SECRETS_BASE="$SECRETS_PATH_CFG" -fi - -DEPLOY_SCRIPT_PATH="$(jq -r '.deploy.deploy_script_path // empty' "$CONF")" -if [[ -z "$DEPLOY_SCRIPT_PATH" || ! -f "$DEPLOY_SCRIPT_PATH" ]]; then - echo "[run-project-hooks][ERROR] deploy.deploy_script_path missing or not a file: ${DEPLOY_SCRIPT_PATH:-}" >&2 - exit 1 -fi - -PHASE_COUNT="$(jq '.deploy.hooks.phases // [] | length' "$CONF")" -if [[ "$PHASE_COUNT" == "0" ]]; then - exec bash "$DEPLOY_SCRIPT_PATH" "$@" -fi - -mapfile -t PHASE_SCRIPTS < <(jq -r '.deploy.hooks.phases[]? | if type == "string" then . elif type == "object" and (.run | type == "string") then .run else empty end' "$CONF") - -if [[ ${#PHASE_SCRIPTS[@]} -eq 0 ]]; then - exec bash "$DEPLOY_SCRIPT_PATH" "$@" -fi - -for rel in "${PHASE_SCRIPTS[@]}"; do - [[ -z "$rel" ]] && continue - phase_path="${REPO_ROOT}/${rel}" - if [[ ! -f "$phase_path" ]]; then - echo "[run-project-hooks][ERROR] Phase script not found: ${phase_path}" >&2 - exit 1 - fi - echo "[run-project-hooks] Running: ${rel} $*" - bash "$phase_path" "$@" -done +exec "$DEPLOY_DIR/orchestrator.sh" "$@"