From 9b6ce337e67e426d3c342a5eb15f7be2554515d7 Mon Sep 17 00:00:00 2001 From: "a.tolmachev" Date: Sat, 11 Apr 2026 12:18:30 +0300 Subject: [PATCH] deploy: split runtime env into individual secrets --- .github/workflows/deploy.yml | 74 ++++++++++++++++++++++++++---------- docs/deployment.md | 41 ++++++++++++++------ docs/runtime-config.md | 11 ++++-- 3 files changed, 90 insertions(+), 36 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index e2e6c29..24ccd99 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -118,33 +118,65 @@ jobs: "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:${{ secrets.DEPLOY_PATH }}/docker-compose.yml" - name: Write environment file + env: + POSTGRES_DB: ${{ secrets.POSTGRES_DB }} + POSTGRES_USER: ${{ secrets.POSTGRES_USER }} + POSTGRES_PASSWORD: ${{ secrets.POSTGRES_PASSWORD }} + CRANK_DATABASE_URL: ${{ secrets.CRANK_DATABASE_URL }} + CRANK_STORAGE_ROOT: ${{ secrets.CRANK_STORAGE_ROOT }} + CRANK_PUBLISH_HOST: ${{ secrets.CRANK_PUBLISH_HOST }} + CRANK_ADMIN_BIND: ${{ secrets.CRANK_ADMIN_BIND }} + CRANK_MCP_BIND: ${{ secrets.CRANK_MCP_BIND }} + CRANK_MCP_REFRESH_MS: ${{ secrets.CRANK_MCP_REFRESH_MS }} + CRANK_LOG_LEVEL: ${{ secrets.CRANK_LOG_LEVEL }} + CRANK_SECRET_PROVIDER: ${{ secrets.CRANK_SECRET_PROVIDER }} + CRANK_MASTER_KEY: ${{ secrets.CRANK_MASTER_KEY }} + CRANK_PUBLIC_BASE_URL: ${{ secrets.CRANK_PUBLIC_BASE_URL }} + CRANK_MCP_PUBLIC_URL: ${{ secrets.CRANK_MCP_PUBLIC_URL }} + CRANK_SESSION_SECRET: ${{ secrets.CRANK_SESSION_SECRET }} + CRANK_PASSWORD_PEPPER: ${{ secrets.CRANK_PASSWORD_PEPPER }} + CRANK_SESSION_TTL_HOURS: ${{ secrets.CRANK_SESSION_TTL_HOURS }} + CRANK_BOOTSTRAP_ADMIN_EMAIL: ${{ secrets.CRANK_BOOTSTRAP_ADMIN_EMAIL }} + CRANK_BOOTSTRAP_ADMIN_PASSWORD: ${{ secrets.CRANK_BOOTSTRAP_ADMIN_PASSWORD }} + CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME: ${{ secrets.CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME }} + CRANK_DEMO_SEED: ${{ secrets.CRANK_DEMO_SEED }} run: | + tmp_env="$(mktemp)" + append_if_set() { + if [ -n "$2" ]; then + printf '%s=%s\n' "$1" "$2" >> "$tmp_env" + fi + } + : > "$tmp_env" + append_if_set POSTGRES_DB "$POSTGRES_DB" + append_if_set POSTGRES_USER "$POSTGRES_USER" + append_if_set POSTGRES_PASSWORD "$POSTGRES_PASSWORD" + append_if_set CRANK_DATABASE_URL "$CRANK_DATABASE_URL" + append_if_set CRANK_STORAGE_ROOT "$CRANK_STORAGE_ROOT" + append_if_set CRANK_PUBLISH_HOST "$CRANK_PUBLISH_HOST" + append_if_set CRANK_ADMIN_BIND "$CRANK_ADMIN_BIND" + append_if_set CRANK_MCP_BIND "$CRANK_MCP_BIND" + append_if_set CRANK_MCP_REFRESH_MS "$CRANK_MCP_REFRESH_MS" + append_if_set CRANK_LOG_LEVEL "$CRANK_LOG_LEVEL" + append_if_set CRANK_SECRET_PROVIDER "$CRANK_SECRET_PROVIDER" + append_if_set CRANK_MASTER_KEY "$CRANK_MASTER_KEY" + append_if_set CRANK_PUBLIC_BASE_URL "$CRANK_PUBLIC_BASE_URL" + append_if_set CRANK_MCP_PUBLIC_URL "$CRANK_MCP_PUBLIC_URL" + append_if_set CRANK_SESSION_SECRET "$CRANK_SESSION_SECRET" + append_if_set CRANK_PASSWORD_PEPPER "$CRANK_PASSWORD_PEPPER" + append_if_set CRANK_SESSION_TTL_HOURS "$CRANK_SESSION_TTL_HOURS" + append_if_set CRANK_BOOTSTRAP_ADMIN_EMAIL "$CRANK_BOOTSTRAP_ADMIN_EMAIL" + append_if_set CRANK_BOOTSTRAP_ADMIN_PASSWORD "$CRANK_BOOTSTRAP_ADMIN_PASSWORD" + append_if_set CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME "$CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME" + append_if_set CRANK_DEMO_SEED "$CRANK_DEMO_SEED" { - printf '%s\n' "${{ secrets.DEPLOY_ENV_FILE }}" printf 'CRANK_ADMIN_API_IMAGE=%s:%s\n' "${{ env.ADMIN_API_IMAGE }}" "${{ env.IMAGE_TAG }}" printf 'CRANK_MCP_SERVER_IMAGE=%s:%s\n' "${{ env.MCP_SERVER_IMAGE }}" "${{ env.IMAGE_TAG }}" printf 'CRANK_UI_IMAGE=%s:%s\n' "${{ env.UI_IMAGE }}" "${{ env.IMAGE_TAG }}" - } | ssh "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \ + } >> "$tmp_env" + cat "$tmp_env" | ssh "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" \ "mkdir -p '${{ secrets.DEPLOY_PATH }}' && cat > '${{ secrets.DEPLOY_PATH }}/.env'" - - - name: Apply deploy overrides - run: | - if [ -n "${{ secrets.DEPLOY_MASTER_KEY }}" ]; then - ssh "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" " - set -e - cd '${{ secrets.DEPLOY_PATH }}' - sed -i '/^CRANK_MASTER_KEY=/d' .env - printf 'CRANK_MASTER_KEY=%s\n' '${{ secrets.DEPLOY_MASTER_KEY }}' >> .env - " - fi - if [ -n "${{ secrets.DEPLOY_DEMO_SEED }}" ]; then - ssh "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" " - set -e - cd '${{ secrets.DEPLOY_PATH }}' - sed -i '/^CRANK_DEMO_SEED=/d' .env - printf 'CRANK_DEMO_SEED=%s\n' '${{ secrets.DEPLOY_DEMO_SEED }}' >> .env - " - fi + rm -f "$tmp_env" - name: Validate required environment variables run: | diff --git a/docs/deployment.md b/docs/deployment.md index 67507aa..5416aa0 100644 --- a/docs/deployment.md +++ b/docs/deployment.md @@ -212,7 +212,7 @@ CD для MVP должен: 1. push в `main` 2. успешный `CI` 3. build and push images в registry -4. upload env и deployment files +4. upload runtime env и deployment files 5. `docker login` на registry на целевом хосте 6. `docker compose config -q` 7. `docker compose pull` @@ -233,19 +233,38 @@ CD для MVP должен: Они используются только для `docker login` на целевом Linux host перед `docker compose pull`. -Для точечных runtime override без переписывания всего `DEPLOY_ENV_FILE` можно использовать -отдельные deployment secrets. Сейчас поддержан: +Монолитный `DEPLOY_ENV_FILE` больше не используется. -- `DEPLOY_MASTER_KEY` -- `DEPLOY_DEMO_SEED` +Workflow собирает `.env` на сервере из отдельных GitHub secrets. Для runtime-переменных нужно +создавать secrets с теми же именами, что и у приложений: -Если задан `DEPLOY_MASTER_KEY`, workflow удаляет `CRANK_MASTER_KEY` из сгенерированного `.env` -и записывает туда значение из `DEPLOY_MASTER_KEY`. Это позволяет ротировать master key или -добавлять его отдельно, не переписывая весь `DEPLOY_ENV_FILE`. +- `POSTGRES_DB` +- `POSTGRES_USER` +- `POSTGRES_PASSWORD` +- `CRANK_DATABASE_URL` +- `CRANK_STORAGE_ROOT` +- `CRANK_PUBLISH_HOST` +- `CRANK_ADMIN_BIND` +- `CRANK_MCP_BIND` +- `CRANK_MCP_REFRESH_MS` +- `CRANK_LOG_LEVEL` +- `CRANK_SECRET_PROVIDER` +- `CRANK_MASTER_KEY` +- `CRANK_PUBLIC_BASE_URL` +- `CRANK_MCP_PUBLIC_URL` +- `CRANK_SESSION_SECRET` +- `CRANK_PASSWORD_PEPPER` +- `CRANK_SESSION_TTL_HOURS` +- `CRANK_BOOTSTRAP_ADMIN_EMAIL` +- `CRANK_BOOTSTRAP_ADMIN_PASSWORD` +- `CRANK_BOOTSTRAP_ADMIN_DISPLAY_NAME` +- `CRANK_DEMO_SEED` -Если задан `DEPLOY_DEMO_SEED`, workflow удаляет `CRANK_DEMO_SEED` из сгенерированного `.env` -и записывает туда значение из `DEPLOY_DEMO_SEED`. Это позволяет временно включать и выключать -demo seed отдельным secret, не переписывая основной production `.env`. +Практически это означает: + +- одна runtime-переменная = один GitHub secret; +- ротация одного секрета не требует переписывать весь набор env; +- `.env` на сервере каждый deploy собирается заново из актуальных secrets. ## 12. Что нельзя забыть diff --git a/docs/runtime-config.md b/docs/runtime-config.md index 2d6e3ea..d67d4c7 100644 --- a/docs/runtime-config.md +++ b/docs/runtime-config.md @@ -142,11 +142,14 @@ Demo/deployment: - `CRANK_DEMO_SEED=true` -В deployment workflow этот флаг можно переопределять отдельно через GitHub secret -`DEPLOY_DEMO_SEED`, не меняя основной `DEPLOY_ENV_FILE`. +В deployment workflow больше не используется монолитный `DEPLOY_ENV_FILE`. -Аналогично `CRANK_MASTER_KEY` можно прокидывать отдельным GitHub secret `DEPLOY_MASTER_KEY`, -если не хочется каждый раз переписывать весь `DEPLOY_ENV_FILE` ради одного ключа. +`.env` на сервере собирается из отдельных GitHub secrets, причем имя секрета совпадает с именем +runtime env-переменной. Это значит, что: + +- `CRANK_MASTER_KEY` хранится как GitHub secret `CRANK_MASTER_KEY`; +- `CRANK_DEMO_SEED` хранится как GitHub secret `CRANK_DEMO_SEED`; +- и так же для остальных runtime-переменных. ## 8.1. Delivery artifacts