feat: validate sign-in emails with BRAIN_ALLOWED_EMAILS_REGEX (#183) #183
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: CD (dev) | |
| on: | |
| push: | |
| branches: [main] | |
| workflow_dispatch: | |
| inputs: | |
| allow_destroy: | |
| description: "Apply even if the plan destroys or replaces resources" | |
| type: boolean | |
| default: false | |
| concurrency: | |
| group: cd-dev | |
| cancel-in-progress: false | |
| permissions: | |
| contents: read | |
| id-token: write # assume AWS roles via OIDC | |
| env: | |
| NO_COLOR: "1" | |
| jobs: | |
| deploy-config: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| environment: ${{ steps.config.outputs.environment }} | |
| aws_region: ${{ steps.config.outputs.aws_region }} | |
| terraform_backend_config: ${{ steps.config.outputs.terraform_backend_config }} | |
| ssm_secret_prefix: ${{ steps.config.outputs.ssm_secret_prefix }} | |
| deploy_group: ${{ steps.config.outputs.deploy_group }} | |
| deploy_id: ${{ steps.config.outputs.deploy_id }} | |
| nango_hostname: ${{ steps.config.outputs.nango_hostname }} | |
| nango_connect_hostname: ${{ steps.config.outputs.nango_connect_hostname }} | |
| brain_hostname: ${{ steps.config.outputs.brain_hostname }} | |
| dozzle_hostname: ${{ steps.config.outputs.dozzle_hostname }} | |
| image_matrix: ${{ steps.config.outputs.image_matrix }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: ./.github/actions/prepare | |
| - name: Resolve deployment config | |
| id: config | |
| run: bun cli/src/scripts/github-deploy-plan.ts github-outputs dev >> "$GITHUB_OUTPUT" | |
| terraform-apply: | |
| needs: deploy-config | |
| runs-on: ubuntu-latest | |
| outputs: | |
| nango_ecr_repository_url: ${{ steps.tf_outputs.outputs.nango_ecr_repository_url }} | |
| brain_ecr_repository_url: ${{ steps.tf_outputs.outputs.brain_ecr_repository_url }} | |
| pg_backup_ecr_repository_url: ${{ steps.tf_outputs.outputs.pg_backup_ecr_repository_url }} | |
| artifacts_bucket: ${{ steps.tf_outputs.outputs.artifacts_bucket }} | |
| github_deploy_role_arn: ${{ steps.tf_outputs.outputs.github_deploy_role_arn }} | |
| data_volume_id: ${{ steps.tf_outputs.outputs.data_volume_id }} | |
| deploy_group_tag: ${{ steps.tf_outputs.outputs.deploy_group_tag }} | |
| defaults: | |
| run: | |
| working-directory: infra/terraform | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: ./.github/actions/prepare | |
| - name: Configure AWS credentials (OIDC) | |
| uses: aws-actions/configure-aws-credentials@v6 | |
| with: | |
| role-to-assume: ${{ vars.AWS_TERRAFORM_ROLE_ARN }} | |
| aws-region: ${{ needs.deploy-config.outputs.aws_region }} | |
| - name: Write Terraform vars | |
| run: bun ../../cli/src/scripts/github-deploy-plan.ts terraform-vars dev > cd.auto.tfvars.json | |
| - run: terraform init -input=false -backend-config=${{ needs.deploy-config.outputs.terraform_backend_config }} | |
| - run: terraform plan -input=false -var-file=cd.auto.tfvars.json -out=tfplan | |
| env: | |
| TF_VAR_google_client_id: ${{ vars.BACKEND_BETTER_AUTH_GOOGLE_CLIENT_ID }} | |
| TF_VAR_google_client_secret: ${{ secrets.BACKEND_BETTER_AUTH_GOOGLE_CLIENT_SECRET }} | |
| # Auto-applying a plan that destroys or replaces resources already | |
| # terminated the instance once (a floating-AMI bump on 2026-06-10). | |
| # Destructive plans need a human: rerun via workflow_dispatch with | |
| # allow_destroy enabled. | |
| - name: Block destructive plans | |
| if: inputs.allow_destroy != true | |
| run: | | |
| destroyed=$(terraform show -json tfplan \ | |
| | jq -r '.resource_changes[] | select(.change.actions | index("delete")) | .address') | |
| if [ -n "$destroyed" ]; then | |
| { | |
| echo '## Destructive terraform plan blocked' | |
| echo 'The plan would destroy or replace:' | |
| echo '```' | |
| echo "$destroyed" | |
| echo '```' | |
| echo 'If intentional, rerun the workflow via workflow_dispatch with `allow_destroy` enabled.' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| echo "::error title=Destructive terraform plan blocked::Plan destroys/replaces: $(echo "$destroyed" | tr '\n' ' ')" | |
| exit 1 | |
| fi | |
| # Applying the saved plan guarantees what was checked is what runs. | |
| - run: terraform apply -input=false tfplan | |
| - name: Capture outputs | |
| id: tf_outputs | |
| run: | | |
| echo "nango_ecr_repository_url=$(terraform output -raw nango_ecr_repository_url)" >> "$GITHUB_OUTPUT" | |
| echo "brain_ecr_repository_url=$(terraform output -raw brain_ecr_repository_url)" >> "$GITHUB_OUTPUT" | |
| echo "pg_backup_ecr_repository_url=$(terraform output -raw pg_backup_ecr_repository_url)" >> "$GITHUB_OUTPUT" | |
| echo "artifacts_bucket=$(terraform output -raw artifacts_bucket)" >> "$GITHUB_OUTPUT" | |
| echo "github_deploy_role_arn=$(terraform output -raw github_deploy_role_arn)" >> "$GITHUB_OUTPUT" | |
| echo "data_volume_id=$(terraform output -raw data_volume_id)" >> "$GITHUB_OUTPUT" | |
| echo "deploy_group_tag=$(terraform output -raw deploy_group_tag)" >> "$GITHUB_OUTPUT" | |
| - name: Outputs | |
| run: | | |
| { | |
| echo '## Terraform outputs' | |
| echo '```' | |
| terraform output | |
| echo '```' | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| build-docker: | |
| needs: [deploy-config, terraform-apply] | |
| runs-on: ubuntu-latest | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| image: ${{ fromJSON(needs.deploy-config.outputs.image_matrix) }} | |
| steps: | |
| - name: Checkout (with submodules) | |
| uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - name: Build and push | |
| id: build | |
| uses: ./.github/actions/build-and-push-docker | |
| with: | |
| aws-region: ${{ needs.deploy-config.outputs.aws_region }} | |
| deploy-role-arn: ${{ needs.terraform-apply.outputs.github_deploy_role_arn }} | |
| ecr-repository-url: ${{ needs.terraform-apply.outputs[matrix.image.repository_output] }} | |
| image-tag: ${{ needs.deploy-config.outputs.deploy_id }} | |
| context: ${{ matrix.image.context }} | |
| dockerfile: ${{ matrix.image.dockerfile }} | |
| cache-scope: ${{ matrix.image.cache_scope }} | |
| build-bundle: | |
| needs: [deploy-config, terraform-apply] | |
| runs-on: ubuntu-latest | |
| outputs: | |
| bundle_url: ${{ steps.meta.outputs.bundle_url }} | |
| steps: | |
| - name: Checkout (with submodules) | |
| uses: actions/checkout@v6 | |
| with: | |
| submodules: recursive | |
| - uses: ./.github/actions/prepare | |
| - name: Configure AWS credentials (OIDC) | |
| uses: aws-actions/configure-aws-credentials@v6 | |
| with: | |
| role-to-assume: ${{ needs.terraform-apply.outputs.github_deploy_role_arn }} | |
| aws-region: ${{ needs.deploy-config.outputs.aws_region }} | |
| - name: Resolve bundle URL | |
| id: meta | |
| env: | |
| ARTIFACTS_BUCKET: ${{ needs.terraform-apply.outputs.artifacts_bucket }} | |
| DEPLOY_ID: ${{ needs.deploy-config.outputs.deploy_id }} | |
| run: bun cli/src/scripts/github-deploy-plan.ts bundle-output dev >> "$GITHUB_OUTPUT" | |
| - name: Package runtime bundle | |
| run: infra/deploy/package_runtime_bundle.sh bundle.tar.gz | |
| - name: Upload bundle to S3 | |
| run: aws s3 cp bundle.tar.gz "${{ steps.meta.outputs.bundle_url }}" | |
| deploy: | |
| needs: | |
| [ | |
| deploy-config, | |
| terraform-apply, | |
| build-docker, | |
| build-bundle, | |
| ] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - uses: ./.github/actions/prepare | |
| - name: Configure AWS credentials (OIDC) | |
| uses: aws-actions/configure-aws-credentials@v6 | |
| with: | |
| role-to-assume: ${{ needs.terraform-apply.outputs.github_deploy_role_arn }} | |
| aws-region: ${{ needs.deploy-config.outputs.aws_region }} | |
| - name: Prepare SSM deploy environment | |
| run: bun cli/src/scripts/github-deploy-plan.ts ssm-env dev >> "$GITHUB_ENV" | |
| env: | |
| DEPLOY_ID: ${{ needs.deploy-config.outputs.deploy_id }} | |
| BUNDLE_URL: ${{ needs.build-bundle.outputs.bundle_url }} | |
| DEPLOY_GROUP: ${{ needs.terraform-apply.outputs.deploy_group_tag }} | |
| DATA_VOLUME_ID: ${{ needs.terraform-apply.outputs.data_volume_id }} | |
| ARTIFACTS_BUCKET: ${{ needs.terraform-apply.outputs.artifacts_bucket }} | |
| NANGO_ECR_REPOSITORY_URL: ${{ needs.terraform-apply.outputs.nango_ecr_repository_url }} | |
| BRAIN_ECR_REPOSITORY_URL: ${{ needs.terraform-apply.outputs.brain_ecr_repository_url }} | |
| PG_BACKUP_ECR_REPOSITORY_URL: ${{ needs.terraform-apply.outputs.pg_backup_ecr_repository_url }} | |
| ACME_EMAIL: ${{ vars.ACME_EMAIL }} | |
| - name: Deploy on instance via SSM | |
| run: infra/deploy/ssm_deploy.sh | |
| deploy-nango-integrations: | |
| needs: [deploy-config, terraform-apply, deploy] | |
| runs-on: ubuntu-latest | |
| env: | |
| NANGO_HOSTNAME: ${{ needs.deploy-config.outputs.nango_hostname }} | |
| NOTION_CLIENT_ID: ${{ secrets.NOTION_CLIENT_ID }} | |
| NOTION_CLIENT_SECRET: ${{ secrets.NOTION_CLIENT_SECRET }} | |
| SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }} | |
| SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }} | |
| SLACK_SCOPES: ${{ vars.SLACK_SCOPES }} | |
| GH_OAUTH_CLIENT_ID: ${{ secrets.GH_OAUTH_CLIENT_ID }} | |
| GH_OAUTH_CLIENT_SECRET: ${{ secrets.GH_OAUTH_CLIENT_SECRET }} | |
| GH_OAUTH_SCOPES: ${{ vars.GH_OAUTH_SCOPES }} | |
| GMAIL_CLIENT_ID: ${{ secrets.GMAIL_CLIENT_ID }} | |
| GMAIL_CLIENT_SECRET: ${{ secrets.GMAIL_CLIENT_SECRET }} | |
| GMAIL_SCOPES: ${{ vars.GMAIL_SCOPES }} | |
| AGENT_SYNC_WEBHOOK_SECRET: ${{ secrets.AGENT_SYNC_WEBHOOK_SECRET }} | |
| NOTION_CONNECTION_ID: ${{ vars.NOTION_CONNECTION_ID }} | |
| SLACK_CONNECTION_ID: ${{ vars.SLACK_CONNECTION_ID }} | |
| GH_CONNECTION_ID: ${{ vars.GH_CONNECTION_ID }} | |
| GMAIL_CONNECTION_ID: ${{ vars.GMAIL_CONNECTION_ID }} | |
| AGENT_CONVERSATIONS_CONNECTION_ID: ${{ vars.AGENT_CONVERSATIONS_CONNECTION_ID }} | |
| NANGO_SECRET_KEY_DEV: ${{ secrets.NANGO_SECRET_KEY_DEV }} | |
| NANGO_DEPLOY_AUTO_CONFIRM: "true" | |
| NANGO_CLI_DEPENDENCY_UPDATE: "false" | |
| steps: | |
| - uses: actions/checkout@v6 | |
| - name: Require Nango API key | |
| run: | | |
| if [ -z "${NANGO_SECRET_KEY_DEV:-}" ]; then | |
| { | |
| echo "## Nango API key required" | |
| echo | |
| echo "Nango has been deployed, but the repository secret \`NANGO_SECRET_KEY_DEV\` is not set." | |
| echo "Sign up in the Nango dashboard at https://${NANGO_HOSTNAME}, copy the generated dev API key, add it as the repository secret \`NANGO_SECRET_KEY_DEV\`, then rerun this workflow." | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| echo "::error title=Nango API key required::Set repository secret NANGO_SECRET_KEY_DEV from the dev API key generated in the Nango dashboard, then rerun the workflow." | |
| exit 1 | |
| fi | |
| echo "::add-mask::${NANGO_SECRET_KEY_DEV}" | |
| - name: Use deployed Nango host | |
| run: echo "NANGO_HOSTPORT=https://${NANGO_HOSTNAME}" >> "$GITHUB_ENV" | |
| - uses: ./.github/actions/prepare | |
| - name: Add Nango integrations | |
| run: bun run company-brain --non-interactive add integrations --target cloud --all | |
| - name: Add Nango syncs | |
| run: bun run company-brain --non-interactive add syncs --target cloud --all |