diff --git a/.gitea/workflows/deploy-env-dns.yml b/.gitea/workflows/deploy-env-dns.yml index 324f8a4..850169d 100644 --- a/.gitea/workflows/deploy-env-dns.yml +++ b/.gitea/workflows/deploy-env-dns.yml @@ -1,3 +1,18 @@ +# ═══════════════════════════════════════════════════════════════════ +# Secret richiesti (a livello repo/org, ereditati con `secrets: inherit`): +# DEPLOY_SSH_KEY, MAVEN_USERNAME, MAVEN_PASSWORD +# INFISICAL_CLIENT_ID, INFISICAL_CLIENT_SECRET, INFISICAL_PROJECT_ID +# +# INFISICAL: i secret applicativi vengono iniettati a runtime dalla CLI +# infisical installata SUL SERVER di deploy (nessun file di secret viene +# scritto su disco). L'environment Infisical coincide con il nome ambiente +# ricavato dai file (docker-compose-.yml / .env_). +# Il file .env_ del repo resta la base: le variabili iniettate da +# Infisical hanno precedenza, perché per docker compose l'ambiente della +# shell vince sui valori di --env-file. +# Se i secret INFISICAL_* non sono configurati il workflow si comporta +# esattamente come prima (nessuna iniezione). +# ═══════════════════════════════════════════════════════════════════ name: Deploy Environment (reusable) on: @@ -40,6 +55,7 @@ jobs: DEPLOY_USER: ${{ vars.DEPLOY_USER }} DEPLOY_BASE_PATH: ${{ vars.DEPLOY_BASE_PATH }} TARGET_HOST: ${{ vars[inputs.host-var] }} + INFISICAL_DOMAIN: ${{ vars.INFISICAL_HOST }} steps: - uses: actions/checkout@v6 @@ -87,6 +103,57 @@ jobs: chmod 600 ~/.ssh/id_ed25519 ssh-keyscan -H "${{ vars[inputs.host-var] }}" >> ~/.ssh/known_hosts 2>/dev/null + # ───────────────────────────────────────────────────────────── + # INFISICAL: login con machine identity (universal auth) sul + # server di deploy, dove la CLI è installata. Il login restituisce + # un access token a vita breve: è l'UNICA credenziale che viaggia + # negli step successivi, mentre client id/secret non escono da qui. + # Le credenziali sono passate via stdin e non come argomenti, così + # non compaiono nella process list del server (`ps`). + # ───────────────────────────────────────────────────────────── + - name: Login Infisical + run: | + HOST="${{ vars[inputs.host-var] }}" + CID="${{ secrets.INFISICAL_CLIENT_ID }}" + CSEC="${{ secrets.INFISICAL_CLIENT_SECRET }}" + PID="${{ vars.INFISICAL_PROJECT_ID }}" + + if [ -z "$CID" ] || [ -z "$CSEC" ] || [ -z "$PID" ]; then + echo "Secret INFISICAL_* non configurati: deploy senza iniezione dei secret (comportamento legacy)." + echo "IRUN_ENV=INFISICAL_TOKEN=" >> $GITHUB_ENV + exit 0 + fi + + if ! ssh ${DEPLOY_USER}@${HOST} "command -v infisical >/dev/null 2>&1"; then + echo "ERRORE: CLI 'infisical' non trovata su ${HOST}." + echo "Installala sul server oppure rimuovi i secret INFISICAL_* per tornare al deploy legacy." + exit 1 + fi + + TOKEN=$(printf '%s\n%s\n%s\n' "$CID" "$CSEC" "$INFISICAL_DOMAIN" | ssh ${DEPLOY_USER}@${HOST} ' + read -r cid + read -r csec + read -r dom + export INFISICAL_UNIVERSAL_AUTH_CLIENT_ID="$cid" + export INFISICAL_UNIVERSAL_AUTH_CLIENT_SECRET="$csec" + export INFISICAL_DOMAIN="$dom" + export INFISICAL_DISABLE_UPDATE_CHECK=true + infisical login --method=universal-auth --silent --plain + ') + + TOKEN=$(echo "$TOKEN" | tr -d '\r' | tail -n 1) + if [ -z "$TOKEN" ]; then + echo "ERRORE: login Infisical fallito su ${INFISICAL_DOMAIN}." + exit 1 + fi + echo "::add-mask::$TOKEN" + + # Prefisso di ambiente riusato da tutti gli ssh successivi: viene + # letto da .irun.sh sul server per decidere se e come iniettare i + # secret. Il token è mascherato nei log. + echo "IRUN_ENV=INFISICAL_TOKEN='${TOKEN}' INFISICAL_PROJECT_ID='${PID}' INFISICAL_ENV='${ENV_NAME}' INFISICAL_DOMAIN='${INFISICAL_DOMAIN}' INFISICAL_DISABLE_UPDATE_CHECK=true" >> $GITHUB_ENV + echo "Login Infisical OK (${INFISICAL_DOMAIN}) - environment '${ENV_NAME}', path '/' ricorsivo." + - name: Docker login sul server run: | HOST="${{ vars[inputs.host-var] }}" @@ -114,9 +181,51 @@ jobs: ssh ${DEPLOY_USER}@${HOST} "mkdir -p ${REMOTE_PATH}" + # Wrapper unico per TUTTI i comandi che devono vedere i secret + # (docker compose pull/up/ps e gli script .wait-healthy.sh / + # .sync-fe-hosts.sh, che a loro volta invocano docker compose). + # Va copiato per primo: viene usato già dallo snapshot qui sotto + # e dallo step di rollback. + cat > /tmp/irun.sh << 'EOF' + #!/bin/sh + # Esegue il comando ricevuto iniettando i secret di Infisical come + # variabili d'ambiente del processo. Per docker compose l'ambiente + # della shell ha precedenza su --env-file: il file .env_ + # del repo resta quindi la base e Infisical sovrascrive/integra. + # Se INFISICAL_TOKEN non e' valorizzato il comando viene eseguito + # tale e quale: comportamento legacy invariato. + set -e + CMD="$*" + + if [ -z "${INFISICAL_TOKEN}" ]; then + exec sh -c "${CMD}" + fi + + if ! command -v infisical >/dev/null 2>&1; then + echo "ERRORE: CLI 'infisical' non installata su questo server." >&2 + exit 1 + fi + + # --recursive non e' presente su tutte le versioni della CLI: lo si + # usa solo se supportato, evitando un "unknown flag" in produzione. + RFLAG="" + if infisical run --help 2>/dev/null | grep -q -- "--recursive"; then + RFLAG="--recursive" + fi + + exec infisical run \ + --projectId="${INFISICAL_PROJECT_ID}" \ + --env="${INFISICAL_ENV}" \ + --path=/ \ + $RFLAG \ + --command="${CMD}" + EOF + scp /tmp/irun.sh ${DEPLOY_USER}@${HOST}:${REMOTE_PATH}/.irun.sh + # Snapshot dello stato corrente PRIMA di sovrascrivere i file ssh ${DEPLOY_USER}@${HOST} " set -e + export ${IRUN_ENV:-INFISICAL_TOKEN=} cd ${REMOTE_PATH} # Se esiste già uno .rollback, non cancellarlo: potrebbe essere l'unica # traccia di un deploy precedente il cui rollback non è andato a buon @@ -130,7 +239,7 @@ jobs: if [ -f ${COMPOSE} ]; then cp -f ${COMPOSE} .rollback/${COMPOSE} [ -f ${ENV_FILE} ] && cp -f ${ENV_FILE} .rollback/${ENV_FILE} || true - ids=\$(docker compose -f ${COMPOSE} ${ENVFLAG} ps -q 2>/dev/null || true) + ids=\$(sh .irun.sh \"docker compose -f ${COMPOSE} ${ENVFLAG} ps -q\" 2>/dev/null || true) if [ -n \"\$ids\" ]; then echo 'services:' > .rollback/pin.yml for cid in \$ids; do @@ -329,8 +438,11 @@ jobs: CBASE="cd ${REMOTE_PATH} && DOCKER_CONFIG=/tmp/.docker-ci docker compose -f ${COMPOSE}" [ -f "$ENV_FILE" ] && CBASE="${CBASE} --env-file ${ENV_FILE}" - ssh ${DEPLOY_USER}@${HOST} "${CBASE} pull" - ssh ${DEPLOY_USER}@${HOST} "${CBASE} up -d ${{ inputs.compose-opts }}" + # I comandi passano da .irun.sh: i secret Infisical vengono iniettati + # nel processo docker compose (interpolazione ${VAR} nel compose file + # e blocchi "environment:"), senza mai finire su disco. + ssh ${DEPLOY_USER}@${HOST} "${IRUN_ENV} sh ${REMOTE_PATH}/.irun.sh '${CBASE} pull'" + ssh ${DEPLOY_USER}@${HOST} "${IRUN_ENV} sh ${REMOTE_PATH}/.irun.sh '${CBASE} up -d ${{ inputs.compose-opts }}'" - name: Post-deploy run: | @@ -366,15 +478,15 @@ jobs: [ -f "$ENV_FILE" ] && CBASE="${CBASE} --env-file ${ENV_FILE}" echo "── Container status (iniziale) ──" - ssh ${DEPLOY_USER}@${HOST} "${CBASE} ps" + ssh ${DEPLOY_USER}@${HOST} "${IRUN_ENV} sh ${REMOTE_PATH}/.irun.sh '${CBASE} ps'" # Attende che tutti i container siano "running" e, se hanno un # healthcheck definito, "healthy" (vedi .wait-healthy.sh, creato # nello step "Prepara server" e condiviso con il rollback). - ssh ${DEPLOY_USER}@${HOST} "cd ${REMOTE_PATH} && sh .wait-healthy.sh ${COMPOSE} ${ENV_FILE} ${TIMEOUT}" + ssh ${DEPLOY_USER}@${HOST} "${IRUN_ENV} sh ${REMOTE_PATH}/.irun.sh 'cd ${REMOTE_PATH} && sh .wait-healthy.sh ${COMPOSE} ${ENV_FILE} ${TIMEOUT}'" echo "── Container status (finale) ──" - ssh ${DEPLOY_USER}@${HOST} "${CBASE} ps" + ssh ${DEPLOY_USER}@${HOST} "${IRUN_ENV} sh ${REMOTE_PATH}/.irun.sh '${CBASE} ps'" # ───────────────────────────────────────────────────────────── # HOSTS: dopo un deploy riuscito, recuperiamo l'IP del container @@ -393,7 +505,7 @@ jobs: COMPOSE="docker-compose-${ENV_NAME}.yml" ENV_FILE=".env_${ENV_NAME}" - ssh ${DEPLOY_USER}@${HOST} "cd ${REMOTE_PATH} && sh .sync-fe-hosts.sh ${COMPOSE} ${ENV_FILE}" + ssh ${DEPLOY_USER}@${HOST} "${IRUN_ENV} sh ${REMOTE_PATH}/.irun.sh 'cd ${REMOTE_PATH} && sh .sync-fe-hosts.sh ${COMPOSE} ${ENV_FILE}'" # ───────────────────────────────────────────────────────────── # CLEANUP: eseguito SOLO se tutti gli step precedenti sono andati @@ -479,6 +591,7 @@ jobs: container_ok=0 ssh ${DEPLOY_USER}@${HOST} " set -e + export ${IRUN_ENV:-INFISICAL_TOKEN=} cd ${REMOTE_PATH} if [ -f .rollback/${COMPOSE} ]; then cp -f .rollback/${COMPOSE} ${COMPOSE} @@ -486,9 +599,9 @@ jobs: PIN='' [ -f .rollback/pin.yml ] && PIN='-f .rollback/pin.yml' echo 'Ripristino container alla versione precedente...' - DOCKER_CONFIG=/tmp/.docker-ci docker compose -f ${COMPOSE} \$PIN ${ENVFLAG} up -d + sh .irun.sh \"DOCKER_CONFIG=/tmp/.docker-ci docker compose -f ${COMPOSE} \$PIN ${ENVFLAG} up -d\" echo 'Attendo che i container ripristinati siano running/healthy...' - sh .wait-healthy.sh ${COMPOSE} ${ENV_FILE} ${TIMEOUT} + sh .irun.sh \"sh .wait-healthy.sh ${COMPOSE} ${ENV_FILE} ${TIMEOUT}\" else echo 'Nessuno snapshot disponibile: nessun rollback container (probabile primo deploy).' fi @@ -503,7 +616,7 @@ jobs: # deploy fallito (o, se prima non c'era una voce, nessuna voce), # puntando a un container che non esiste più. hosts_ok=0 - ssh ${DEPLOY_USER}@${HOST} "cd ${REMOTE_PATH} && sh .sync-fe-hosts.sh ${COMPOSE} ${ENV_FILE}" || hosts_ok=1 + ssh ${DEPLOY_USER}@${HOST} "${IRUN_ENV:-INFISICAL_TOKEN=} sh ${REMOTE_PATH}/.irun.sh 'cd ${REMOTE_PATH} && sh .sync-fe-hosts.sh ${COMPOSE} ${ENV_FILE}'" || hosts_ok=1 # Cleanup config temporaneo (best-effort, non deve mascherare l'esito del rollback) ssh ${DEPLOY_USER}@${HOST} "rm -rf /tmp/.docker-ci" || true