diff --git a/.github/workflows/kamal-deploy.yml b/.github/workflows/kamal-deploy.yml deleted file mode 100644 index 278602e2fc..0000000000 --- a/.github/workflows/kamal-deploy.yml +++ /dev/null @@ -1,129 +0,0 @@ -name: Kamal Deploy -concurrency: - group: deploy-to-kamal - cancel-in-progress: false - -on: - workflow_dispatch: - #push: - # branches: - # - main - -jobs: - Deploy: - runs-on: ubuntu-latest - environment: production - env: - DOCKER_BUILDKIT: 1 - IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }} - WEBAPP_URL: ${{ vars.WEBAPP_URL }} - MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} - ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }} - SHORT_URL_BASE: ${{ vars.SHORT_URL_BASE }} - MAIL_FROM: ${{ secrets.MAIL_FROM }} - SMTP_HOST: ${{ secrets.SMTP_HOST }} - SMTP_PORT: ${{ secrets.SMTP_PORT }} - SMTP_USER: ${{ secrets.SMTP_USER }} - SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} - PRIVACY_URL: ${{ vars.PRIVACY_URL }} - TERMS_URL: ${{ vars.TERMS_URL }} - IMPRINT_URL: ${{ vars.IMPRINT_URL }} - GITHUB_ID: ${{ secrets.FB_GITHUB_ID }} - GITHUB_SECRET: ${{ secrets.FB_GITHUB_SECRET }} - GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} - GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} - AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }} - AZUREAD_CLIENT_SECRET: ${{ secrets.AZUREAD_CLIENT_SECRET }} - AZUREAD_TENANT_ID: ${{ secrets.AZUREAD_TENANT_ID }} - OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }} - OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }} - OIDC_ISSUER: ${{ secrets.OIDC_ISSUER }} - OIDC_DISPLAY_NAME: ${{ secrets.OIDC_DISPLAY_NAME }} - OIDC_SIGNING_ALGORITHM: ${{ secrets.OIDC_SIGNING_ALGORITHM }} - CRON_SECRET: ${{ secrets.CRON_SECRET }} - ASSET_PREFIX_URL: ${{ vars.ASSET_PREFIX_URL }} - NOTION_OAUTH_CLIENT_ID: ${{ secrets.NOTION_OAUTH_CLIENT_ID }} - NOTION_OAUTH_CLIENT_SECRET: ${{ secrets.NOTION_OAUTH_CLIENT_SECRET }} - SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }} - SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }} - STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} - STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} - GOOGLE_SHEETS_CLIENT_ID: ${{ secrets.GOOGLE_SHEETS_CLIENT_ID }} - GOOGLE_SHEETS_CLIENT_SECRET: ${{ secrets.GOOGLE_SHEETS_CLIENT_SECRET }} - GOOGLE_SHEETS_REDIRECT_URL: ${{ secrets.GOOGLE_SHEETS_REDIRECT_URL }} - AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }} - ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }} - DEFAULT_ORGANIZATION_ID: ${{ vars.DEFAULT_ORGANIZATION_ID }} - CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }} - CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }} - NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }} - NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }} - NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }} - NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }} - NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }} - NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }} - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - NODE_ENV: production - CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }} - CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }} - S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} - S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} - S3_REGION: ${{ vars.S3_REGION }} - S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }} - OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }} - RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }} - KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} - DB_HOST: ${{ secrets.DB_HOST }} - DB_USER: ${{ secrets.DB_USER }} - DB_PASSWORD: ${{ secrets.DB_PASSWORD }} - DB_NAME: ${{ secrets.DB_NAME }} - REDIS_URL: ${{ secrets.REDIS_URL }} - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.3.0 - bundler-cache: true - - - name: Install dependencies - run: | - gem install kamal - - - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v2 - - - name: Create builder - run: docker buildx create --use --name formbricks-gh-actions-builder - if: steps.buildx.outputs.should_create_builder == 'true' - - - name: Push env variables to Kamal - run: | - kamal() { command kamal "$@" -c kamal/deploy.yml; } - kamal env push - - - name: Run deploy command - run: | - kamal() { command kamal "$@" -c kamal/deploy.yml; } - set +e - DEPLOY_OUTPUT=$(kamal deploy 2>&1) - DEPLOY_EXIT_CODE=$? - echo "$DEPLOY_OUTPUT" - if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then - echo "Deployment reported healthy container. Considering as success." - kamal lock release - exit 0 - else - exit $DEPLOY_EXIT_CODE - fi - shell: bash diff --git a/.github/workflows/kamal-setup.yml b/.github/workflows/kamal-setup.yml deleted file mode 100644 index 37a4aae6a2..0000000000 --- a/.github/workflows/kamal-setup.yml +++ /dev/null @@ -1,126 +0,0 @@ -name: Kamal Setup -concurrency: - group: setup-kamal - cancel-in-progress: false - -on: - workflow_dispatch: # Only to be triggered when accessories are updated - -jobs: - Setup: - runs-on: ubuntu-latest - environment: production - env: - DOCKER_BUILDKIT: 1 - IS_FORMBRICKS_CLOUD: ${{ vars.IS_FORMBRICKS_CLOUD }} - WEBAPP_URL: ${{ vars.WEBAPP_URL }} - DATABASE_URL: ${{ secrets.DATABASE_URL }} - MIGRATE_DATABASE_URL: ${{ secrets.MIGRATE_DATABASE_URL }} - NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} - ENCRYPTION_KEY: ${{ secrets.ENCRYPTION_KEY }} - SHORT_URL_BASE: ${{ vars.SHORT_URL_BASE }} - MAIL_FROM: ${{ secrets.MAIL_FROM }} - SMTP_HOST: ${{ secrets.SMTP_HOST }} - SMTP_PORT: ${{ secrets.SMTP_PORT }} - SMTP_USER: ${{ secrets.SMTP_USER }} - SMTP_PASSWORD: ${{ secrets.SMTP_PASSWORD }} - PRIVACY_URL: ${{ vars.PRIVACY_URL }} - TERMS_URL: ${{ vars.TERMS_URL }} - IMPRINT_URL: ${{ vars.IMPRINT_URL }} - GITHUB_ID: ${{ secrets.FB_GITHUB_ID }} - GITHUB_SECRET: ${{ secrets.FB_GITHUB_SECRET }} - GOOGLE_CLIENT_ID: ${{ secrets.GOOGLE_CLIENT_ID }} - GOOGLE_CLIENT_SECRET: ${{ secrets.GOOGLE_CLIENT_SECRET }} - AZUREAD_CLIENT_ID: ${{ secrets.AZUREAD_CLIENT_ID }} - AZUREAD_CLIENT_SECRET: ${{ secrets.AZUREAD_CLIENT_SECRET }} - AZUREAD_TENANT_ID: ${{ secrets.AZUREAD_TENANT_ID }} - OIDC_CLIENT_ID: ${{ secrets.OIDC_CLIENT_ID }} - OIDC_CLIENT_SECRET: ${{ secrets.OIDC_CLIENT_SECRET }} - OIDC_ISSUER: ${{ secrets.OIDC_ISSUER }} - OIDC_DISPLAY_NAME: ${{ secrets.OIDC_DISPLAY_NAME }} - OIDC_SIGNING_ALGORITHM: ${{ secrets.OIDC_SIGNING_ALGORITHM }} - CRON_SECRET: ${{ secrets.CRON_SECRET }} - ASSET_PREFIX_URL: ${{ vars.ASSET_PREFIX_URL }} - NOTION_OAUTH_CLIENT_ID: ${{ secrets.NOTION_OAUTH_CLIENT_ID }} - NOTION_OAUTH_CLIENT_SECRET: ${{ secrets.NOTION_OAUTH_CLIENT_SECRET }} - SLACK_CLIENT_ID: ${{ secrets.SLACK_CLIENT_ID }} - SLACK_CLIENT_SECRET: ${{ secrets.SLACK_CLIENT_SECRET }} - STRIPE_SECRET_KEY: ${{ secrets.STRIPE_SECRET_KEY }} - STRIPE_WEBHOOK_SECRET: ${{ secrets.STRIPE_WEBHOOK_SECRET }} - GOOGLE_SHEETS_CLIENT_ID: ${{ secrets.GOOGLE_SHEETS_CLIENT_ID }} - GOOGLE_SHEETS_CLIENT_SECRET: ${{ secrets.GOOGLE_SHEETS_CLIENT_SECRET }} - GOOGLE_SHEETS_REDIRECT_URL: ${{ secrets.GOOGLE_SHEETS_REDIRECT_URL }} - AIRTABLE_CLIENT_ID: ${{ secrets.AIRTABLE_CLIENT_ID }} - ENTERPRISE_LICENSE_KEY: ${{ secrets.ENTERPRISE_LICENSE_KEY }} - DEFAULT_ORGANIZATION_ID: ${{ vars.DEFAULT_ORGANIZATION_ID }} - CUSTOMER_IO_API_KEY: ${{ secrets.CUSTOMER_IO_API_KEY }} - CUSTOMER_IO_SITE_ID: ${{ secrets.CUSTOMER_IO_SITE_ID }} - NEXT_PUBLIC_POSTHOG_API_KEY: ${{ vars.NEXT_PUBLIC_POSTHOG_API_KEY }} - NEXT_PUBLIC_POSTHOG_API_HOST: ${{ vars.NEXT_PUBLIC_POSTHOG_API_HOST }} - NEXT_PUBLIC_FORMBRICKS_API_HOST: ${{ vars.NEXT_PUBLIC_FORMBRICKS_API_HOST }} - NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID }} - NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID: ${{ vars.NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID }} - NEXT_PUBLIC_SENTRY_DSN: ${{ vars.NEXT_PUBLIC_SENTRY_DSN }} - SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} - NODE_ENV: production - CLOUDFLARE_EMAIL: ${{ secrets.CLOUDFLARE_EMAIL }} - CLOUDFLARE_DNS_API_TOKEN: ${{ secrets.CLOUDFLARE_DNS_API_TOKEN }} - S3_ACCESS_KEY: ${{ secrets.S3_ACCESS_KEY }} - S3_SECRET_KEY: ${{ secrets.S3_SECRET_KEY }} - S3_REGION: ${{ vars.S3_REGION }} - S3_BUCKET_NAME: ${{ vars.S3_BUCKET_NAME }} - OPENTELEMETRY_LISTENER_URL: ${{ vars.OPENTELEMETRY_LISTENER_URL }} - RATE_LIMITING_DISABLED: ${{ vars.RATE_LIMITING_DISABLED }} - KAMAL_REGISTRY_PASSWORD: ${{ secrets.KAMAL_REGISTRY_PASSWORD }} - DB_HOST: ${{ secrets.DB_HOST }} - DB_USER: ${{ secrets.DB_USER }} - DB_PASSWORD: ${{ secrets.DB_PASSWORD }} - DB_NAME: ${{ secrets.DB_NAME }} - REDIS_URL: ${{ secrets.REDIS_URL }} - - steps: - - name: Checkout code - uses: actions/checkout@v3 - - - name: Set up Ruby - uses: ruby/setup-ruby@v1 - with: - ruby-version: 3.3.0 - bundler-cache: true - - - name: Install dependencies - run: | - gem install kamal - - - uses: webfactory/ssh-agent@v0.9.0 - with: - ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} - - - name: Set up Docker Buildx - id: buildx - uses: docker/setup-buildx-action@v2 - - - name: Create builder - run: docker buildx create --use --name formbricks-gh-actions-builder - if: steps.buildx.outputs.should_create_builder == 'true' - - - name: Push env variables to Kamal - run: | - kamal() { command kamal "$@" -c kamal/deploy.yml; } - kamal env push - - - name: Run setup command - run: | - kamal() { command kamal "$@" -c kamal/deploy.yml; } - set +e - DEPLOY_OUTPUT=$(kamal setup 2>&1) - DEPLOY_EXIT_CODE=$? - echo "$DEPLOY_OUTPUT" - if [[ "$DEPLOY_OUTPUT" == *"container not unhealthy (healthy)"* ]]; then - echo "Deployment reported healthy container. Considering as success." - kamal lock release - exit 0 - else - exit $DEPLOY_EXIT_CODE - fi - shell: bash diff --git a/.kamal/hooks/post-deploy.sample b/.kamal/hooks/post-deploy.sample deleted file mode 100755 index 75efafc10e..0000000000 --- a/.kamal/hooks/post-deploy.sample +++ /dev/null @@ -1,14 +0,0 @@ -#!/bin/sh - -# A sample post-deploy hook -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_ROLE (if set) -# KAMAL_DESTINATION (if set) -# KAMAL_RUNTIME - -echo "$KAMAL_PERFORMER deployed $KAMAL_VERSION to $KAMAL_DESTINATION in $KAMAL_RUNTIME seconds" diff --git a/.kamal/hooks/post-traefik-reboot.sample b/.kamal/hooks/post-traefik-reboot.sample deleted file mode 100755 index e3d9e3ccbe..0000000000 --- a/.kamal/hooks/post-traefik-reboot.sample +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo "Rebooted Traefik on $KAMAL_HOSTS" diff --git a/.kamal/hooks/pre-build.sample b/.kamal/hooks/pre-build.sample deleted file mode 100755 index f87d81130b..0000000000 --- a/.kamal/hooks/pre-build.sample +++ /dev/null @@ -1,51 +0,0 @@ -#!/bin/sh - -# A sample pre-build hook -# -# Checks: -# 1. We have a clean checkout -# 2. A remote is configured -# 3. The branch has been pushed to the remote -# 4. The version we are deploying matches the remote -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_ROLE (if set) -# KAMAL_DESTINATION (if set) - -if [ -n "$(git status --porcelain)" ]; then - echo "Git checkout is not clean, aborting..." >&2 - git status --porcelain >&2 - exit 1 -fi - -first_remote=$(git remote) - -if [ -z "$first_remote" ]; then - echo "No git remote set, aborting..." >&2 - exit 1 -fi - -current_branch=$(git branch --show-current) - -if [ -z "$current_branch" ]; then - echo "Not on a git branch, aborting..." >&2 - exit 1 -fi - -remote_head=$(git ls-remote $first_remote --tags $current_branch | cut -f1) - -if [ -z "$remote_head" ]; then - echo "Branch not pushed to remote, aborting..." >&2 - exit 1 -fi - -if [ "$KAMAL_VERSION" != "$remote_head" ]; then - echo "Version ($KAMAL_VERSION) does not match remote HEAD ($remote_head), aborting..." >&2 - exit 1 -fi - -exit 0 diff --git a/.kamal/hooks/pre-connect.sample b/.kamal/hooks/pre-connect.sample deleted file mode 100755 index 18e61d7e5a..0000000000 --- a/.kamal/hooks/pre-connect.sample +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env ruby - -# A sample pre-connect check -# -# Warms DNS before connecting to hosts in parallel -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_ROLE (if set) -# KAMAL_DESTINATION (if set) -# KAMAL_RUNTIME - -hosts = ENV["KAMAL_HOSTS"].split(",") -results = nil -max = 3 - -elapsed = Benchmark.realtime do - results = hosts.map do |host| - Thread.new do - tries = 1 - - begin - Socket.getaddrinfo(host, 0, Socket::AF_UNSPEC, Socket::SOCK_STREAM, nil, Socket::AI_CANONNAME) - rescue SocketError - if tries < max - puts "Retrying DNS warmup: #{host}" - tries += 1 - sleep rand - retry - else - puts "DNS warmup failed: #{host}" - host - end - end - - tries - end - end.map(&:value) -end - -retries = results.sum - hosts.size -nopes = results.count { |r| r == max } - -puts "Prewarmed %d DNS lookups in %.2f sec: %d retries, %d failures" % [ hosts.size, elapsed, retries, nopes ] diff --git a/.kamal/hooks/pre-deploy.sample b/.kamal/hooks/pre-deploy.sample deleted file mode 100755 index 1b280c719e..0000000000 --- a/.kamal/hooks/pre-deploy.sample +++ /dev/null @@ -1,109 +0,0 @@ -#!/usr/bin/env ruby - -# A sample pre-deploy hook -# -# Checks the Github status of the build, waiting for a pending build to complete for up to 720 seconds. -# -# Fails unless the combined status is "success" -# -# These environment variables are available: -# KAMAL_RECORDED_AT -# KAMAL_PERFORMER -# KAMAL_VERSION -# KAMAL_HOSTS -# KAMAL_COMMAND -# KAMAL_SUBCOMMAND -# KAMAL_ROLE (if set) -# KAMAL_DESTINATION (if set) - -# Only check the build status for production deployments -if ENV["KAMAL_COMMAND"] == "rollback" || ENV["KAMAL_DESTINATION"] != "production" - exit 0 -end - -require "bundler/inline" - -# true = install gems so this is fast on repeat invocations -gemfile(true, quiet: true) do - source "https://rubygems.org" - - gem "octokit" - gem "faraday-retry" -end - -MAX_ATTEMPTS = 72 -ATTEMPTS_GAP = 10 - -def exit_with_error(message) - $stderr.puts message - exit 1 -end - -class GithubStatusChecks - attr_reader :remote_url, :git_sha, :github_client, :combined_status - - def initialize - @remote_url = `git config --get remote.origin.url`.strip.delete_prefix("https://github.com/") - @git_sha = `git rev-parse HEAD`.strip - @github_client = Octokit::Client.new(access_token: ENV["GITHUB_TOKEN"]) - refresh! - end - - def refresh! - @combined_status = github_client.combined_status(remote_url, git_sha) - end - - def state - combined_status[:state] - end - - def first_status_url - first_status = combined_status[:statuses].find { |status| status[:state] == state } - first_status && first_status[:target_url] - end - - def complete_count - combined_status[:statuses].count { |status| status[:state] != "pending"} - end - - def total_count - combined_status[:statuses].count - end - - def current_status - if total_count > 0 - "Completed #{complete_count}/#{total_count} checks, see #{first_status_url} ..." - else - "Build not started..." - end - end -end - - -$stdout.sync = true - -puts "Checking build status..." -attempts = 0 -checks = GithubStatusChecks.new - -begin - loop do - case checks.state - when "success" - puts "Checks passed, see #{checks.first_status_url}" - exit 0 - when "failure" - exit_with_error "Checks failed, see #{checks.first_status_url}" - when "pending" - attempts += 1 - end - - exit_with_error "Checks are still pending, gave up after #{MAX_ATTEMPTS * ATTEMPTS_GAP} seconds" if attempts == MAX_ATTEMPTS - - puts checks.current_status - sleep(ATTEMPTS_GAP) - checks.refresh! - end -rescue Octokit::NotFound - exit_with_error "Build status could not be found" -end diff --git a/.kamal/hooks/pre-traefik-reboot.sample b/.kamal/hooks/pre-traefik-reboot.sample deleted file mode 100755 index 8cfda6d915..0000000000 --- a/.kamal/hooks/pre-traefik-reboot.sample +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -echo "Rebooting Traefik on $KAMAL_HOSTS..." diff --git a/kamal/README.md b/kamal/README.md deleted file mode 100644 index ba2ceaf0e8..0000000000 --- a/kamal/README.md +++ /dev/null @@ -1,147 +0,0 @@ -# Kamal Setup - -1. Initiate a Linux instance & Get it's - - - Public IP address - - SSH credentials - -2. Edit the IP address & SSH config in Kamal `deploy.yml` file. -3. Test you SSH status with: - - ```sh - kamal lock status - ``` - -4. If the above returns a non-zero error code, run the below: - - ```sh - eval `ssh-agent -s` - ssh-add .pem - ``` - -5. Now test the SSH status again: - - ```sh - kamal lock status - ``` - - This should now run successfully & exit with 0. - -6. Generate a Classic Personal Access Token for `container:write` & `container:read` for your Image Registry (DockerHub, GHCR, etc.) & add it to your environment variables (.env file). Also update the Registry details in the `deploy.yml`. - -7. If your SSH user is a non-root user, run the below command to add the user to the docker group: - - ```sh - sudo usermod -aG docker ${USER} - ``` - - > Note: The above needs to be ran on the Cloud VM. There is an open Issue on Kamal for the same [here](https://github.com/basecamp/kamal/issues/405) - -> Run the below for SSL config the first time - -```sh -sudo mkdir -p /letsencrypt && sudo touch /letsencrypt/acme.json && sudo chmod 600 /letsencrypt/acme.json -``` - -> Run this command to create the private bridge network used by kamal to reference containers on one instance - -```sh -docker network create -d bridge private -``` - -8. Make sure you have docker buildx locally on your machine where you run the kamal CLI from! - -9. Voila! You are all set to deploy your application to the cloud with Kamal! 🚀 - - ```sh - kamal setup -c kamal/deploy.yml - ``` - - This will setup the cloud VM with all the necessary tools & dependencies to run your application. - -> Make sure to run `kamal env push` before a `kamal deploy` to push the latest environment variables to the cloud VM. - -### Debugging for Kamal - -- If you run into an error such as: - - ```sh - failed to solve: cannot copy to non-directory: - ``` - - Then simply run `pnpm clean` & try again. - -- Make sure your Database accepts connection from the cloud VM. You can do this by adding the VM's IP address to the `Allowed Hosts` in your Database settings. - -- If you get an error such as: - - ```sh - Lock failed: failed to acquire lock: lockfile already exists - ``` - - Then simply run `kamal lock release -c kamal/deploy.yml` & try again. - -- If you run into: - - ```sh - No config found - ``` - - Then simply add the following at the end of the command: `-c kamal/deploy.yml` - -For further details, refer to the [Kamal Documentation](https://kamal-deploy.org/docs/configuration) or reach out to us on our [Discord](https://formbricks.com/discord) - -# Rollback to a Previous Version - -```sh -kamal rollback [git_commit_hash_to_rollback_to] -c kamal/deploy.yml -``` - -## View Formbricks Server logs with Kamal - -```sh -kamal app logs -c kamal/deploy.yml -``` - -# Configure Memory Metrics on AWS using CW Agent - -1. Install the CloudWatch Agent on the EC2 instance - -```sh -wget https://amazoncloudwatch-agent.s3.amazonaws.com/ubuntu/amd64/latest/amazon-cloudwatch-agent.deb -sudo dpkg -i -E ./amazon-cloudwatch-agent.deb -``` - -2. Attach the IAM role of `CloudWatchFullAccessv2` to the EC2 instance - -3. Edit the CloudWatch Agent config file - -```sh -sudo nano /opt/aws/amazon-cloudwatch-agent/bin/config.json -``` - -4. Add the below config to the `config.json` file - -```json -{ - "metrics": { - "metrics_collected": { - "mem": { - "measurement": ["mem_used_percent"], - "metrics_collection_interval": 60 - } - }, - "append_dimensions": { - "InstanceId": "${aws:InstanceId}" - } - } -} -``` - -5. Fetch the config & start the CloudWatch Agent - -```sh -sudo /opt/aws/amazon-cloudwatch-agent/bin/amazon-cloudwatch-agent-ctl -a fetch-config -m ec2 -c file:/opt/aws/amazon-cloudwatch-agent/bin/config.json -s -``` - -6. Now go to Cloudwatch > Metrics > All Metrics > Custom Namespaces > CWAgent > InstanceId > {InstanceId} > Tick the Checkbox next to it > Graph above will be updated diff --git a/kamal/deploy.yml b/kamal/deploy.yml deleted file mode 100644 index d24f644190..0000000000 --- a/kamal/deploy.yml +++ /dev/null @@ -1,154 +0,0 @@ -# Name of your application. Used to uniquely configure containers. -service: formbricks-kamal - -# Name of the container image. -image: formbricks/formbricks-cloud - -# Deploy to these servers. -servers: - web: # Use a named role, so it can be used as entrypoint by Traefik - hosts: - - 18.196.187.144 - - 18.196.172.27 - - 35.157.124.188 - - 18.199.207.103 - - ec2-18-194-217-29.eu-central-1.compute.amazonaws.com - - ec2-3-64-56-61.eu-central-1.compute.amazonaws.com - - ec2-3-122-60-81.eu-central-1.compute.amazonaws.com - labels: - traefik.http.routers.formbricks-kamal.entrypoints: web - options: - network: "private" - -# Credentials for your image host. -registry: - # Specify the registry server, if you're not using Docker Hub - server: ghcr.io - username: mattinannt - # Always use an access token rather than real password when possible. - password: - - KAMAL_REGISTRY_PASSWORD - -# Inject ENV variables into containers (secrets come from .env). -# Remember to run `kamal env push` after making changes! -env: - clear: - REDIS_HTTP_URL: http://formbricks-kamal-webdis:7379 - secret: - - IS_FORMBRICKS_CLOUD - - WEBAPP_URL - - DATABASE_URL - - MIGRATE_DATABASE_URL - - NEXTAUTH_SECRET - - ENCRYPTION_KEY - - SHORT_URL_BASE - - MAIL_FROM - - SMTP_HOST - - SMTP_PORT - - SMTP_USER - - SMTP_PASSWORD - - PRIVACY_URL - - TERMS_URL - - IMPRINT_URL - - GITHUB_ID - - GITHUB_SECRET - - GOOGLE_CLIENT_ID - - GOOGLE_CLIENT_SECRET - - AZUREAD_CLIENT_ID - - AZUREAD_CLIENT_SECRET - - AZUREAD_TENANT_ID - - OIDC_CLIENT_ID - - OIDC_CLIENT_SECRET - - OIDC_ISSUER - - OIDC_DISPLAY_NAME - - OIDC_SIGNING_ALGORITHM - - CRON_SECRET - - ASSET_PREFIX_URL - - NOTION_OAUTH_CLIENT_ID - - NOTION_OAUTH_CLIENT_SECRET - - SLACK_CLIENT_ID - - SLACK_CLIENT_SECRET - - STRIPE_SECRET_KEY - - STRIPE_WEBHOOK_SECRET - - GOOGLE_SHEETS_CLIENT_ID - - GOOGLE_SHEETS_CLIENT_SECRET - - GOOGLE_SHEETS_REDIRECT_URL - - AIRTABLE_CLIENT_ID - - ENTERPRISE_LICENSE_KEY - - DEFAULT_ORGANIZATION_ID - - CUSTOMER_IO_API_KEY - - CUSTOMER_IO_SITE_ID - - NEXT_PUBLIC_POSTHOG_API_KEY - - NEXT_PUBLIC_POSTHOG_API_HOST - - NEXT_PUBLIC_FORMBRICKS_API_HOST - - NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID - - NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID - - OPENTELEMETRY_LISTENER_URL - - NEXT_PUBLIC_SENTRY_DSN - - CLOUDFLARE_EMAIL - - CLOUDFLARE_DNS_API_TOKEN - - S3_ACCESS_KEY - - S3_SECRET_KEY - - S3_REGION - - S3_BUCKET_NAME - - RATE_LIMITING_DISABLED - - DB_HOST - - DB_USER - - DB_PASSWORD - - DB_NAME - - SENTRY_AUTH_TOKEN - - REDIS_URL - -# Use a different ssh user than root -ssh: - user: ubuntu - -# Configure builder setup. -builder: - context: . - dockerfile: ./apps/web/Dockerfile - args: - - NEXT_PUBLIC_POSTHOG_API_KEY - - NEXT_PUBLIC_POSTHOG_API_HOST - - NEXT_PUBLIC_FORMBRICKS_API_HOST - - NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID - - NEXT_PUBLIC_FORMBRICKS_ONBOARDING_SURVEY_ID - - NEXT_PUBLIC_SENTRY_DSN - - ASSET_PREFIX_URL - - SENTRY_AUTH_TOKEN - multiarch: false - cache: - type: registry - options: mode=max,image-manifest=true,oci-mediatypes=true - -traefik: - args: - entryPoints.web.address: ":80" - options: - network: "private" - -# Use accessory services (secrets come from .env). -accessories: - webdis: - image: nicolas/webdis:0.1.22 - roles: - - web - env: - secret: - - REDIS_URL - cmd: > - /bin/sh -c ' - REDIS_HOST=$(echo $REDIS_URL | awk -F "[:/]" "{print \$4}") && - wget -O /usr/local/bin/webdis.json https://github.com/nicolasff/webdis/raw/0.1.22/webdis.json && - sed -i "s/\"redis_host\":.*/\"redis_host\": \"$REDIS_HOST\",/" /usr/local/bin/webdis.json && - sed -i "s/\"logfile\":.*/\"logfile\": \"\/dev\/stderr\"/" /usr/local/bin/webdis.json && - /usr/local/bin/webdis /usr/local/bin/webdis.json' - port: 7379 - options: - network: "private" - -healthcheck: - path: /health - port: 3000 - max_attempts: 15 - interval: 20s