diff --git a/README.md b/README.md index f44e90b..e84396a 100644 --- a/README.md +++ b/README.md @@ -40,11 +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)** : +**Orchestration générique (méthodologie ia_dev → orchestrateur projet)** : -- **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). +- **deploy/lib/deploy-methodology.sh** : environnements autorisés (`test` \| `pprod` \| `prod`), validations communes ; évolutions « même méthodologie / qualité / séquences » pour tous les projets s’ajoutent ici (ou libs sœurs), pas dans les dépôts applicatifs. +- **deploy.sh** : `./deploy/deploy.sh [options…]` — méthodologie + `IA_PROJECT_ID`, puis **orchestrator.sh**. +- **orchestrator.sh** : secrets depuis `conf.json`, puis **`exec`** du **script orchestrateur du dépôt cible** : `deploy.project_orchestrator_path` (relatif à `repository_root`). Un seul point d’entrée côté projet ; ce script enchaîne les sous-scripts métier. **Repli** : sans `project_orchestrator_path`, ancien modèle `hooks.phases` ou `deploy.deploy_script_path`. +- **run-project-hooks.sh** : délègue à **orchestrator.sh** (compatibilité). - **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] `. diff --git a/deploy/deploy.sh b/deploy/deploy.sh index e162948..2beef71 100755 --- a/deploy/deploy.sh +++ b/deploy/deploy.sh @@ -1,7 +1,6 @@ #!/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...] +# Generic deploy entry from ia_dev: shared methodology (envs, contract), then ia_dev orchestrator → project orchestrator. +# Usage (from ia_dev root): ./deploy/deploy.sh [options…] # Example: ./deploy/deploy.sh lecoffreio test --import-v1 --skipSetupHost set -euo pipefail @@ -9,9 +8,12 @@ SCRIPT_REAL="$(readlink -f "${BASH_SOURCE[0]:-$0}" 2>/dev/null || realpath "${BA DEPLOY_DIR="$(cd "$(dirname "$SCRIPT_REAL")" && pwd)" IA_DEV_ROOT="$(cd "$DEPLOY_DIR/.." && pwd)" +# shellcheck source=lib/deploy-methodology.sh +source "${DEPLOY_DIR}/lib/deploy-methodology.sh" + if [[ $# -lt 2 ]]; then echo "[deploy][ERROR] Missing arguments" >&2 - echo "Usage: $0 [options passed to each phase / fallback deploy script]" >&2 + echo "Usage: $0 [options passed to project orchestrator]" >&2 echo "Example: $0 lecoffreio test --import-v1 --skipSetupHost" >&2 exit 1 fi @@ -22,6 +24,9 @@ if [[ ! -f "$CONF" ]]; then exit 1 fi +ia_dev_deploy_assert_env_literal "${2}" || exit 1 +ia_dev_deploy_log_methodology_banner + export IA_PROJECT_ID="$1" shift exec "$DEPLOY_DIR/orchestrator.sh" "$@" diff --git a/deploy/lib/README.md b/deploy/lib/README.md index c03ce82..59037f3 100644 --- a/deploy/lib/README.md +++ b/deploy/lib/README.md @@ -13,7 +13,15 @@ Optional `deploy_script_tee_log_if_requested ` — re 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. +## `deploy-methodology.sh` + +Shared contract for all managed projects: allowed envs (`test` \| `pprod` \| `prod`), validation helpers. Sourced by **`deploy.sh`** and **`orchestrator.sh`**. Extend only with an explicit decision (new env = conf + doc migration). + +Quality gates and longer sequences that are identical across projects should be added here (or in small `deploy/lib/deploy-*.sh` peers) over time — not in project repos. + ## 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. +- **`deploy.sh`** : `./deploy/deploy.sh [args]` — applies methodology (env validation, banner), sets `IA_PROJECT_ID`, then **`exec orchestrator.sh`**. +- **`orchestrator.sh`** : exports secrets from conf, then **`exec`** the **project orchestrator** `repository_root` + `deploy.project_orchestrator_path` (relative path) with the same `"$@"`. **Legacy** : if `project_orchestrator_path` is missing, uses `deploy.hooks.phases` or `deploy.deploy_script_path`. **`run-project-hooks.sh`** execs `orchestrator.sh` for backward compatibility. + +Project orchestrator = single script in the target repo that sequences project-specific steps (`deploy.sh` LeCoffre today, or a dedicated `orchestrate-project.sh` later). diff --git a/deploy/lib/deploy-methodology.sh b/deploy/lib/deploy-methodology.sh new file mode 100644 index 0000000..4efea5a --- /dev/null +++ b/deploy/lib/deploy-methodology.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +# Shared deploy methodology for all ia_dev–managed projects: environments, quality bar, ordering contract. +# Sourced by ia_dev/deploy/deploy.sh and ia_dev/deploy/orchestrator.sh — no project-specific paths here. +# Project-specific sequencing lives only in the repository's project orchestrator (deploy.project_orchestrator_path). + +# Environments are fixed across projects; extend only with an explicit decision and conf migration. +IA_DEV_DEPLOY_ENVS=(test pprod prod) + +# ia_dev_deploy_env_is_allowed — exit 0 if allowed +ia_dev_deploy_env_is_allowed() { + local e="${1:-}" + local x + for x in "${IA_DEV_DEPLOY_ENVS[@]}"; do + if [[ "$e" == "$x" ]]; then + return 0 + fi + done + return 1 +} + +# ia_dev_deploy_assert_first_arg_env "$@" — first positional must be test|pprod|prod; stderr + exit 1 otherwise +ia_dev_deploy_assert_first_arg_env() { + if [[ $# -lt 1 ]]; then + echo "[ia_dev][deploy][ERROR] Missing (expected: test | pprod | prod)" >&2 + return 1 + fi + local env_arg="$1" + ia_dev_deploy_assert_env_literal "$env_arg" +} + +# ia_dev_deploy_assert_env_literal — validate a single env token +ia_dev_deploy_assert_env_literal() { + local env_arg="${1:-}" + if [[ -z "$env_arg" ]]; then + echo "[ia_dev][deploy][ERROR] Missing (expected: test | pprod | prod)" >&2 + return 1 + fi + if ! ia_dev_deploy_env_is_allowed "$env_arg"; then + echo "[ia_dev][deploy][ERROR] Invalid env '${env_arg}' (allowed: ${IA_DEV_DEPLOY_ENVS[*]})" >&2 + return 1 + fi + return 0 +} + +# ia_dev_deploy_log_methodology_banner — optional trace for support +ia_dev_deploy_log_methodology_banner() { + echo "[ia_dev][deploy] Methodology: envs={${IA_DEV_DEPLOY_ENVS[*]}} ; project orchestrator invoked after conf + secrets export (see orchestrator.sh)." +} diff --git a/deploy/orchestrator.sh b/deploy/orchestrator.sh index a1dbe64..9ac6722 100755 --- a/deploy/orchestrator.sh +++ b/deploy/orchestrator.sh @@ -1,9 +1,9 @@ #!/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). +# ia_dev orchestrator: applies shared methodology, then invokes exactly one project orchestrator +# (deploy.project_orchestrator_path relative to repository_root), which sequences project-specific scripts. +# Legacy: if project_orchestrator_path is absent, falls back to deploy.hooks.phases or deploy.deploy_script_path. +# Usage: orchestrator.sh [options…] — passed unchanged to the project orchestrator. +# Requires: IA_PROJECT_ID, IA_DEV_ROOT (or callers 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}")" @@ -12,11 +12,17 @@ IA_DEV_ROOT="$(cd "$DEPLOY_DIR/.." && pwd)" _ORCH_TAG="[orchestrator]" +# shellcheck source=lib/deploy-methodology.sh +source "${DEPLOY_DIR}/lib/deploy-methodology.sh" + if [[ -z "${IA_PROJECT_ID:-}" ]]; then echo "${_ORCH_TAG}[ERROR] IA_PROJECT_ID is not set" >&2 exit 1 fi +ia_dev_deploy_assert_first_arg_env "$@" || exit 1 +ia_dev_deploy_log_methodology_banner + # shellcheck source=../lib/project_config.sh source "${IA_DEV_ROOT}/lib/project_config.sh" # shellcheck source=../lib/project_git_root_from_conf.sh @@ -35,7 +41,7 @@ if [[ -z "$CONF" || ! -f "$CONF" ]]; then fi if ! command -v jq >/dev/null 2>&1; then - echo "${_ORCH_TAG}[ERROR] jq is required to read deploy.hooks.phases" >&2 + echo "${_ORCH_TAG}[ERROR] jq is required to read deploy.* from conf.json" >&2 exit 1 fi @@ -46,6 +52,22 @@ if [[ -n "$SECRETS_PATH_CFG" && "$SECRETS_PATH_CFG" != "null" && -d "$SECRETS_PA fi DEPLOY_SCRIPT_PATH="$(jq -r '.deploy.deploy_script_path // empty' "$CONF")" +PROJECT_ORCH_REL="$(jq -r '.deploy.project_orchestrator_path // empty' "$CONF")" + +if [[ -n "$PROJECT_ORCH_REL" && "$PROJECT_ORCH_REL" != "null" ]]; then + PROJECT_ORCH_ABS="${REPO_ROOT}/${PROJECT_ORCH_REL}" + if [[ ! -f "$PROJECT_ORCH_ABS" ]]; then + echo "${_ORCH_TAG}[ERROR] deploy.project_orchestrator_path not a file: ${PROJECT_ORCH_ABS}" >&2 + exit 1 + fi + PHASE_NON_EMPTY="$(jq '.deploy.hooks.phases // [] | length' "$CONF")" + if [[ "$PHASE_NON_EMPTY" != "0" ]]; then + echo "${_ORCH_TAG}[WARN] deploy.hooks.phases is non-empty but project_orchestrator_path takes precedence; phases are ignored." >&2 + fi + echo "${_ORCH_TAG} Project orchestrator: ${PROJECT_ORCH_REL} $*" + exec bash "$PROJECT_ORCH_ABS" "$@" +fi + 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 @@ -53,6 +75,7 @@ fi PHASE_COUNT="$(jq '.deploy.hooks.phases // [] | length' "$CONF")" if [[ "$PHASE_COUNT" == "0" ]]; then + echo "${_ORCH_TAG}[WARN] deploy.project_orchestrator_path unset; falling back to deploy_script_path only." >&2 exec bash "$DEPLOY_SCRIPT_PATH" "$@" fi @@ -69,6 +92,6 @@ for rel in "${PHASE_SCRIPTS[@]}"; do echo "${_ORCH_TAG}[ERROR] Phase script not found: ${phase_path}" >&2 exit 1 fi - echo "${_ORCH_TAG} Running: ${rel} $*" + echo "${_ORCH_TAG} Running (legacy phases): ${rel} $*" bash "$phase_path" "$@" done diff --git a/projects/README.md b/projects/README.md index 8867901..e34d94e 100644 --- a/projects/README.md +++ b/projects/README.md @@ -45,7 +45,9 @@ One JSON file per project: `projects//conf.json` (e.g. `projects/lecoffreio/ | `project_path` | no | **Absolute** path to the project deploy root (e.g. `/home/desk/code/lecoffre_ng_test/deploy`). | | `build_dirs` | no | **Absolute** paths to directories where `npm run build` is run. | | `deploy.scripts_path` | no | **Absolute** path to deploy scripts (e.g. `…/deploy/scripts_v2`). | -| `deploy.deploy_script_path` | no | **Absolute** path to deploy.sh. | +| `deploy.deploy_script_path` | no | **Absolute** path to deploy.sh (fallback / tooling; keep aligned with project orchestrator). | +| `deploy.project_orchestrator_path` | recommended | **Relative to `deploy.repository_root`** : single project orchestrator script (sequences project-specific deploy steps). Preferred over `deploy.hooks.phases`. | +| `deploy.hooks.phases` | legacy | Optional list of scripts relative to `repository_root`; used only if `project_orchestrator_path` is unset. | | `deploy.secrets_path` | no | **Absolute** path to the project’s `.secrets` directory. | | `version.package_json_paths` | no | **Absolute** paths to package.json files to bump. | | `mail.imap_bridge_env` | no | **Relative to ia_dev root**: path to IMAP bridge env file (e.g. `.secrets/gitea-issues/imap-bridge.env`). | diff --git a/projects/algo/conf.json b/projects/algo/conf.json index d600c0a..0ad3437 100644 --- a/projects/algo/conf.json +++ b/projects/algo/conf.json @@ -11,10 +11,8 @@ "repository_root": "/home/desk/code/algo", "scripts_path": "/home/desk/code/algo/deploy/scripts_v2", "deploy_script_path": "/home/desk/code/algo/deploy/scripts_v2/deploy.sh", - "secrets_path": "/home/desk/code/algo/.secrets", - "hooks": { - "phases": [] - } + "project_orchestrator_path": "deploy/scripts_v2/deploy.sh", + "secrets_path": "/home/desk/code/algo/.secrets" }, "version": { "package_json_paths": [ diff --git a/projects/enso/conf.json b/projects/enso/conf.json index 684e6fa..1835564 100644 --- a/projects/enso/conf.json +++ b/projects/enso/conf.json @@ -11,10 +11,8 @@ "repository_root": "/home/desk/code/enso", "scripts_path": "/home/desk/code/enso/deploy/scripts_v2", "deploy_script_path": "/home/desk/code/enso/deploy/scripts_v2/deploy.sh", - "secrets_path": "/home/desk/code/enso/.secrets", - "hooks": { - "phases": [] - } + "project_orchestrator_path": "deploy/scripts_v2/deploy.sh", + "secrets_path": "/home/desk/code/enso/.secrets" }, "version": { "package_json_paths": [ diff --git a/projects/lecoffreio/conf.json b/projects/lecoffreio/conf.json index 14ab726..dea1534 100644 --- a/projects/lecoffreio/conf.json +++ b/projects/lecoffreio/conf.json @@ -11,12 +11,8 @@ "repository_root": "/home/desk/code/lecoffre_ng_test", "scripts_path": "/home/desk/code/lecoffre_ng_test/deploy/scripts_v2", "deploy_script_path": "/home/desk/code/lecoffre_ng_test/deploy/scripts_v2/deploy.sh", - "secrets_path": "/home/desk/code/ia_dev/projects/lecoffreio/.secrets", - "hooks": { - "phases": [ - "deploy/scripts_v2/deploy.sh" - ] - } + "project_orchestrator_path": "deploy/scripts_v2/deploy.sh", + "secrets_path": "/home/desk/code/ia_dev/projects/lecoffreio/.secrets" }, "version": { "package_json_paths": [