Skip to content

feat: validate sign-in emails with BRAIN_ALLOWED_EMAILS_REGEX (#183) #183

feat: validate sign-in emails with BRAIN_ALLOWED_EMAILS_REGEX (#183)

feat: validate sign-in emails with BRAIN_ALLOWED_EMAILS_REGEX (#183) #183

Workflow file for this run

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