Compare commits

...

9 Commits

Author SHA1 Message Date
Piyush Jain eeda5ac361 chore(one-click-setup): add minio support 2025-03-28 12:55:30 +05:30
Piyush Jain c8a1f6f0de fix: once-clik-k8s setup and helm chart redis
- update chart to add minio dependency
- fix variable issues
2025-03-27 14:55:49 +05:30
Piyush Jain 4fc81cb2a2 fix: once-clik-k8s setup and helm chart redis
- fix variables
2025-03-27 14:17:38 +05:30
Piyush Jain e1f958c905 fix: once-clik-k8s setup and helm chart redis
- fixes redis auth issues with self hosted redis
- fixes few one-click-setup
2025-03-27 13:15:54 +05:30
Matthias Nannt 4b9a819abf feat: move one-click setup to k8s 2025-03-26 23:29:05 +09:00
Piyush Jain 709917eb8f chore: fix OneLeet compliance and update self-hosting docs (#5045)
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
2025-03-26 10:37:56 +01:00
Johannes 3ba70122d5 docs: update hidden field docs (#5067) 2025-03-26 01:45:31 -07:00
Dhruwang Jariwala 5ff025543e fix: static ttf in link survey preview (#5054) 2025-03-26 05:42:30 +00:00
Anshuman Pandey 896d5bad12 fix: adds network checks for the react-native sdk (#5034)
Co-authored-by: Dhruwang <dhruwangjariwala18@gmail.com>
Co-authored-by: Matthias Nannt <mail@matthiasnannt.com>
2025-03-26 04:51:42 +00:00
23 changed files with 1654 additions and 1844 deletions
-1
View File
@@ -29,7 +29,6 @@ export const GET = async (req: NextRequest) => {
<h2 tw="flex flex-col text-[8] sm:text-4xl font-bold tracking-tight text-slate-900 text-left mt-15">
{name}
</h2>
<span tw="text-slate-600 text-xl">Complete in ~ 4 minutes</span>
</div>
</div>
<div tw="flex justify-end mr-10 ">
+1 -1
View File
@@ -32,7 +32,7 @@ export const getSurveyMetadata = reactCache(async (surveyId: string) =>
return survey;
} catch (error) {
if (error instanceof Prisma.PrismaClientKnownRequestError) {
logger.error(error, "Error getting survey metadata");
logger.error(error);
throw new DatabaseError(error.message);
}
throw error;
-416
View File
@@ -1,416 +0,0 @@
#!/bin/env bash
set -e
ubuntu_version=$(lsb_release -a 2>/dev/null | grep -v "No LSB modules are available." | grep "Description:" | awk -F "Description:\t" '{print $2}')
install_formbricks() {
# Friendly welcome
echo "🧱 Welcome to the Formbricks Setup Script"
echo ""
echo "🛸 Fasten your seatbelts! We're setting up your Formbricks environment on your $ubuntu_version server."
echo ""
# Remove any old Docker installations, without stopping the script if they're not found
echo "🧹 Time to sweep away any old Docker installations."
sudo apt-get remove docker docker-engine docker.io containerd runc >/dev/null 2>&1 || true
# Update package list
echo "🔄 Updating your package list."
sudo apt-get update >/dev/null 2>&1
# Install dependencies
echo "📦 Installing the necessary dependencies."
sudo apt-get install -y \
ca-certificates \
curl \
gnupg \
lsb-release >/dev/null 2>&1
# Set up Docker's official GPG key & stable repository
echo "🔑 Adding Docker's official GPG key and setting up the stable repository."
sudo mkdir -m 0755 -p /etc/apt/keyrings >/dev/null 2>&1
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg >/dev/null 2>&1
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null 2>&1
# Update package list again
echo "🔄 Updating your package list again."
sudo apt-get update >/dev/null 2>&1
# Install Docker
echo "🐳 Installing Docker."
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin >/dev/null 2>&1
# Test Docker installation
echo "🚀 Testing your Docker installation."
if docker --version >/dev/null 2>&1; then
echo "🎉 Docker is installed!"
else
echo "❌ Docker is not installed. Please install Docker before proceeding."
exit 1
fi
# Adding your user to the Docker group
echo "🐳 Adding your user to the Docker group to avoid using sudo with docker commands."
sudo groupadd docker >/dev/null 2>&1 || true
sudo usermod -aG docker $USER >/dev/null 2>&1
echo "🎉 Hooray! Docker is all set and ready to go. You're now ready to run your Formbricks instance!"
mkdir -p formbricks && cd formbricks
echo "📁 Created Formbricks Quickstart directory at ./formbricks."
# Ask the user for their domain name
echo "🔗 Please enter your domain name for the SSL certificate (🚨 do NOT enter the protocol (http/https/etc)):"
read domain_name
echo "🔗 Do you want us to set up an HTTPS certificate for you? [Y/n]"
read https_setup
https_setup=$(echo "$https_setup" | tr '[:upper:]' '[:lower:]')
# Set default value for HTTPS setup
if [[ -z $https_setup ]]; then
https_setup="y"
fi
if [[ $https_setup == "y" ]]; then
echo "🔗 Please make sure that the domain points to the server's IP address and that ports 80 & 443 are open in your server's firewall. Is everything set up? [Y/n]"
read dns_setup
dns_setup=$(echo "$dns_setup" | tr '[:upper:]' '[:lower:]')
# Set default value for DNS setup
if [[ -z $dns_setup ]]; then
dns_setup="y"
fi
if [[ $dns_setup == "y" ]]; then
echo "💡 Please enter your email address for the SSL certificate:"
read email_address
echo "🔗 Do you want to enforce HTTPS (HSTS)? [Y/n]"
read hsts_enabled
hsts_enabled=$(echo "$hsts_enabled" | tr '[:upper:]' '[:lower:]')
# Set default value for HSTS
if [[ -z $hsts_enabled ]]; then
hsts_enabled="y"
fi
else
echo "❌ Ports 80 & 443 are not open. We can't help you in providing the SSL certificate."
https_setup="n"
hsts_enabled="n"
fi
else
https_setup="n"
hsts_enabled="n"
fi
# Ask for HSTS configuration for HTTPS redirection if custom certificate is used
if [[ $https_setup == "n" ]]; then
echo "You have chosen not to set up HTTPS certificate for your domain. Please make sure to set up HTTPS on your own. You can refer to the Formbricks documentation(https://formbricks.com/docs/self-hosting/custom-ssl) for more information."
echo "🔗 Do you want to enforce HTTPS (HSTS)? [Y/n]"
read hsts_enabled
hsts_enabled=$(echo "$hsts_enabled" | tr '[:upper:]' '[:lower:]')
# Set default value for HSTS
if [[ -z $hsts_enabled ]]; then
hsts_enabled="y"
fi
fi
# Installing Traefik
echo "🚗 Configuring Traefik..."
if [[ $hsts_enabled == "y" ]]; then
hsts_middlewares="middlewares:
- hstsHeader"
http_redirection="http:
redirections:
entryPoint:
to: websecure
scheme: https
permanent: true"
else
hsts_middlewares=""
http_redirection=""
fi
if [[ $https_setup == "y" ]]; then
certResolver="certResolver: default"
certificates_resolvers="certificatesResolvers:
default:
acme:
email: $email_address
storage: acme.json
caServer: "https://acme-v01.api.letsencrypt.org/directory"
tlsChallenge: {}"
else
certResolver=""
certificates_resolvers=""
fi
cat <<EOT >traefik.yaml
entryPoints:
web:
address: ":80"
$http_redirection
websecure:
address: ":443"
http:
tls:
$certResolver
options: default
$hsts_middlewares
providers:
docker:
watch: true
exposedByDefault: false
file:
directory: /
$certificates_resolvers
EOT
cat <<EOT >traefik-dynamic.yaml
# configuring min TLS version
tls:
options:
default:
minVersion: VersionTLS12
cipherSuites:
# TLS 1.2 Ciphers
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
- TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
- TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
# TLS 1.3 Ciphers (These are automatically used for TLS 1.3 connections)
- TLS_AES_128_GCM_SHA256
- TLS_AES_256_GCM_SHA384
- TLS_CHACHA20_POLY1305_SHA256
# Fallback
- TLS_FALLBACK_SCSV
EOT
echo "💡 Created traefik.yaml and traefik-dynamic.yaml file."
if [[ $https_setup == "y" ]]; then
touch acme.json
chmod 600 acme.json
echo "💡 Created acme.json file with correct permissions."
fi
# Prompt for email service setup
read -p "📧 Do you want to set up the email service? You will need SMTP credentials for the same! [y/N]" email_service
email_service=$(echo "$email_service" | tr '[:upper:]' '[:lower:]')
# Set default value for email service setup
if [[ -z $email_service ]]; then
email_service="n"
fi
if [[ $email_service == "y" ]]; then
echo "Please provide the following email service details: "
echo -n "Enter your SMTP configured Email ID: "
read mail_from
echo -n "Enter your SMTP configured Email Name: "
read mail_from_name
echo -n "Enter your SMTP Host URL: "
read smtp_host
echo -n "Enter your SMTP Host Port: "
read smtp_port
echo -n "Enter your SMTP username: "
read smtp_user
echo -n "Enter your SMTP password: "
read smtp_password
echo -n "Enable Authenticated SMTP? Enter 1 for yes and 0 for no(default is 1): "
read smtp_authenticated
echo -n "Enable Secure SMTP (use SSL)? Enter 1 for yes and 0 for no: "
read smtp_secure_enabled
else
mail_from=""
mail_from_name=""
smtp_host=""
smtp_port=""
smtp_user=""
smtp_password=""
smtp_authenticated=1
smtp_secure_enabled=0
fi
echo "📥 Downloading docker-compose.yml from Formbricks GitHub repository..."
curl -o docker-compose.yml https://raw.githubusercontent.com/formbricks/formbricks/main/docker/docker-compose.yml
echo "🚙 Updating docker-compose.yml with your custom inputs..."
sed -i "/WEBAPP_URL:/s|WEBAPP_URL:.*|WEBAPP_URL: \"https://$domain_name\"|" docker-compose.yml
sed -i "/NEXTAUTH_URL:/s|NEXTAUTH_URL:.*|NEXTAUTH_URL: \"https://$domain_name\"|" docker-compose.yml
nextauth_secret=$(openssl rand -hex 32) && sed -i "/NEXTAUTH_SECRET:$/s/NEXTAUTH_SECRET:.*/NEXTAUTH_SECRET: $nextauth_secret/" docker-compose.yml
echo "🚗 NEXTAUTH_SECRET updated successfully!"
encryption_key=$(openssl rand -hex 32) && sed -i "/ENCRYPTION_KEY:$/s/ENCRYPTION_KEY:.*/ENCRYPTION_KEY: $encryption_key/" docker-compose.yml
echo "🚗 ENCRYPTION_KEY updated successfully!"
cron_secret=$(openssl rand -hex 32) && sed -i "/CRON_SECRET:$/s/CRON_SECRET:.*/CRON_SECRET: $cron_secret/" docker-compose.yml
echo "🚗 CRON_SECRET updated successfully!"
if [[ -n $mail_from ]]; then
sed -i "s|# MAIL_FROM:|MAIL_FROM: \"$mail_from\"|" docker-compose.yml
sed -i "s|# MAIL_FROM_NAME:|MAIL_FROM_NAME: \"$mail_from_name\"|" docker-compose.yml
sed -i "s|# SMTP_HOST:|SMTP_HOST: \"$smtp_host\"|" docker-compose.yml
sed -i "s|# SMTP_PORT:|SMTP_PORT: \"$smtp_port\"|" docker-compose.yml
sed -i "s|# SMTP_SECURE_ENABLED:|SMTP_SECURE_ENABLED: $smtp_secure_enabled|" docker-compose.yml
sed -i "s|# SMTP_USER:|SMTP_USER: \"$smtp_user\"|" docker-compose.yml
sed -i "s|# SMTP_PASSWORD:|SMTP_PASSWORD: \"$smtp_password\"|" docker-compose.yml
sed -i "s|# SMTP_AUTHENTICATED:|SMTP_AUTHENTICATED: $smtp_authenticated|" docker-compose.yml
fi
awk -v domain_name="$domain_name" -v hsts_enabled="$hsts_enabled" '
/formbricks:/,/^ *$/ {
if ($0 ~ /depends_on:/) {
inserting_labels=1
}
if (inserting_labels && ($0 ~ /ports:/)) {
print " labels:"
print " - \"traefik.enable=true\" # Enable Traefik for this service"
print " - \"traefik.http.routers.formbricks.rule=Host(`" domain_name "`)\" # Use your actual domain or IP"
print " - \"traefik.http.routers.formbricks.entrypoints=websecure\" # Use the websecure entrypoint (port 443 with TLS)"
print " - \"traefik.http.routers.formbricks.tls=true\" # Enable TLS"
print " - \"traefik.http.routers.formbricks.tls.certresolver=default\" # Specify the certResolver"
print " - \"traefik.http.services.formbricks.loadbalancer.server.port=3000\" # Forward traffic to Formbricks on port 3000"
if (hsts_enabled == "y") {
print " - \"traefik.http.middlewares.hstsHeader.headers.stsSeconds=31536000\" # Set HSTS (HTTP Strict Transport Security) max-age to 1 year (31536000 seconds)"
print " - \"traefik.http.middlewares.hstsHeader.headers.forceSTSHeader=true\" # Ensure the HSTS header is always included in responses"
print " - \"traefik.http.middlewares.hstsHeader.headers.stsPreload=true\" # Allow the domain to be preloaded in browser HSTS preload list"
print " - \"traefik.http.middlewares.hstsHeader.headers.stsIncludeSubdomains=true\" # Apply HSTS policy to all subdomains as well"
} else {
print " - \"traefik.http.routers.formbricks_http.entrypoints=web\" # Use the web entrypoint (port 80)"
print " - \"traefik.http.routers.formbricks_http.rule=Host(`" domain_name "`)\" # Use your actual domain or IP"
}
inserting_labels=0
}
print
next
}
/^volumes:/ {
print " traefik:"
print " image: \"traefik:v2.7\""
print " restart: always"
print " container_name: \"traefik\""
print " depends_on:"
print " - formbricks"
print " ports:"
print " - \"80:80\""
print " - \"443:443\""
print " - \"8080:8080\""
print " volumes:"
print " - ./traefik.yaml:/traefik.yaml"
print " - ./traefik-dynamic.yaml:/traefik-dynamic.yaml"
print " - ./acme.json:/acme.json"
print " - /var/run/docker.sock:/var/run/docker.sock:ro"
print ""
}
1
' docker-compose.yml >tmp.yml && mv tmp.yml docker-compose.yml
newgrp docker <<END
docker compose up -d
echo "🔗 To edit more variables and deeper config, go to the formbricks/docker-compose.yml, edit the file, and restart the container!"
echo "🚨 Make sure you have set up the DNS records as well as inbound rules for the domain name and IP address of this instance."
echo ""
echo "🎉 All done! Please setup your Formbricks instance by visiting your domain at https://$domain_name. You can check the status of Formbricks & Traefik with 'cd formbricks && sudo docker compose ps.'"
END
}
uninstall_formbricks() {
echo "🗑️ Preparing to Uninstalling Formbricks..."
read -p "Are you sure you want to uninstall Formbricks? This will delete all the data associated with it! (yes/no): " uninstall_confirmation
uninstall_confirmation=$(echo "$uninstall_confirmation" | tr '[:upper:]' '[:lower:]')
if [[ $uninstall_confirmation == "yes" ]]; then
cd formbricks
sudo docker compose down
cd ..
sudo rm -rf formbricks
echo "🛑 Formbricks uninstalled successfully!"
else
echo "❌ Uninstalling Formbricks has been cancelled."
fi
}
stop_formbricks() {
echo "🛑 Stopping Formbricks..."
cd formbricks
sudo docker compose down
echo "🎉 Formbricks instance stopped successfully!"
}
update_formbricks() {
echo "🔄 Updating Formbricks..."
cd formbricks
sudo docker compose pull
sudo docker compose down
sudo docker compose up -d
echo "🎉 Formbricks updated successfully!"
echo "🎉 Check the status of Formbricks & Traefik with 'cd formbricks && sudo docker compose logs.'"
}
restart_formbricks() {
echo "🔄 Restarting Formbricks..."
cd formbricks
sudo docker compose restart
echo "🎉 Formbricks restarted successfully!"
}
get_logs() {
echo "📃 Getting Formbricks logs..."
cd formbricks
sudo docker compose logs
}
case "$1" in
install)
install_formbricks
;;
update)
update_formbricks
;;
stop)
stop_formbricks
;;
restart)
restart_formbricks
;;
logs)
get_logs
;;
uninstall)
uninstall_formbricks
;;
*)
echo "🚀 Executing default step of installing Formbricks"
install_formbricks
;;
esac
+10 -15
View File
@@ -19,17 +19,12 @@ Ensure you have the following before proceeding:
## 1. Installation Steps
<Steps>
<Step title="Clone the Helm Chart">
```sh
git clone https://github.com/formbricks/formbricks
cd helm-chart
```
</Step>
<Step title="Install with Default Configuration">
```sh
helm install formbricks ./ -n formbricks --create-namespace
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace
```
> **Note:** To specify specific version use `--version` flag. E.g., `--version 1.0.0`. Starting from 3.5.0, the chart is available on the GitHub Container Registry (GHCR).
By default:
- PostgreSQL and Redis are deployed within the cluster.
- Secrets are dynamically generated and stored as Kubernetes Secrets.
@@ -37,7 +32,7 @@ By default:
<Step title="Install with an Enterprise License">
```sh
helm install formbricks ./ -n formbricks --create-namespace --set enterprise.licenseKey="YOUR_LICENSE_KEY"
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace --set enterprise.licenseKey="YOUR_LICENSE_KEY"
```
</Step>
</Steps>
@@ -107,7 +102,7 @@ externalSecret:
Install with:
```sh
helm install formbricks ./ -n formbricks --create-namespace -f values.yaml
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace -f values.yaml
```
---
@@ -129,7 +124,7 @@ redis:
```
Install with:
```sh
helm install formbricks ./ -n formbricks --create-namespace -f values.yaml
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace -f values.yaml
```
---
@@ -147,7 +142,7 @@ redis:
```
Apply with:
```sh
helm install formbricks ./ -n formbricks --create-namespace -f values.yaml
helm install formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace -f values.yaml
```
---
@@ -155,17 +150,17 @@ helm install formbricks ./ -n formbricks --create-namespace -f values.yaml
## 4. Upgrading the Deployment
To apply changes:
```sh
helm upgrade formbricks ./ -n formbricks
helm upgrade formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks
```
### Scaling Resources
```sh
helm upgrade formbricks ./ -n formbricks --set deployment.resources.limits.cpu=2 --set deployment.resources.limits.memory=4Gi
helm upgrade formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --set deployment.resources.limits.cpu=2 --set deployment.resources.limits.memory=4Gi
```
### Enabling Autoscaling
```sh
helm upgrade formbricks ./ -n formbricks --set autoscaling.enabled=true --set autoscaling.minReplicas=3 --set autoscaling.maxReplicas=10
helm upgrade formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --set autoscaling.enabled=true --set autoscaling.minReplicas=3 --set autoscaling.maxReplicas=10
```
---
@@ -20,9 +20,7 @@ icon: "eye-slash"
![Filled Hidden Fields](/images/xm-and-surveys/surveys/general-features/hidden-fields/filled-hidden-fields.webp)
## Set Hidden Field in Responses
### Link Surveys
### Set Hidden Field in Link Surveys
Single Hidden Field:
@@ -36,20 +34,8 @@ Multiple Hidden Fields:
sh https://formbricks.com/clin3dxja02k8l80hpwmx4bjy?screen=landing_page&job=Founder
```
### App & Website Surveys
For in-product surveys, you can set hidden fields in the response by adding them to the `formbricks.track` call:
<CodeGroup>
```JS action.js
formbricks.track("my event", {
hiddenFields: {
screen: "landing_page",
job: "Founder"
},
});
```
</CodeGroup>
### Website & App Surveys
We're reworking our approach to setting hidden fields in Website & App Surveys.
## View Hidden Fields in Responses
+5 -2
View File
@@ -5,5 +5,8 @@ dependencies:
- name: redis
repository: oci://registry-1.docker.io/bitnamicharts
version: 20.11.2
digest: sha256:6233567e6d133fd87585de7cb11f835125ab649fc7979eac7b17d4b2881f54dc
generated: "2025-03-06T15:48:20.190945+05:30"
- name: minio
repository: oci://registry-1.docker.io/bitnamicharts
version: 15.0.7
digest: sha256:ce42b49e555fb89d365b44de289a2020c6cc8696eaa2aab6f5317b9ee8558ec2
generated: "2025-03-27T14:35:28.229585+05:30"
+4
View File
@@ -26,3 +26,7 @@ dependencies:
version: 20.11.2
repository: "oci://registry-1.docker.io/bitnamicharts"
condition: redis.enabled
- name: minio
repository: "oci://registry-1.docker.io/bitnamicharts"
version: 15.0.7
condition: minio.enabled
+15 -1
View File
@@ -79,7 +79,7 @@ spec:
terminationGracePeriodSeconds: {{ .Values.deployment.terminationGracePeriodSeconds | default 30 }}
containers:
- name: {{ template "formbricks.name" . }}
image: "{{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag | default .Chart.AppVersion }}"
image: {{ .Values.deployment.image.repository }}:{{ .Values.deployment.image.tag | default .Chart.AppVersion | default "latest" }}
imagePullPolicy: {{ .Values.deployment.image.pullPolicy }}
{{- if .Values.deployment.command }}
command:
@@ -127,6 +127,20 @@ spec:
{{- end }}
{{- end }}
env:
{{- if .Values.minio.enabled }}
- name: S3_ACCESS_KEY
valueFrom:
secretKeyRef:
name: formbricks-minio
key: root-user
- name: S3_SECRET_KEY
valueFrom:
secretKeyRef:
name: formbricks-minio
key: root-password
- name: S3_BUCKET_NAME
value: formbricks
{{- end }}
{{- range $key, $value := .Values.deployment.env }}
- name: {{ include "formbricks.tplvalues.render" ( dict "value" $key "context" $ ) }}
{{- if kindIs "string" $value }}
+16
View File
@@ -0,0 +1,16 @@
{{- if and (.Values.prometheusRule).enabled (.Capabilities.APIVersions.Has "monitoring.coreos.com/v1") -}}
---
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: {{ template "formbricks.name" . }}
namespace: {{ include "formbricks.namespace" . }}
labels:
{{- include "formbricks.labels" $ | nindent 4 }}
{{- if .Values.prometheusRule.additionalLabels }}
{{ toYaml .Values.prometheusRule.additionalLabels | indent 4 }}
{{- end }}
spec:
groups:
{{ toYaml .Values.prometheusRule.groups | indent 4 }}
{{- end -}}
+5 -2
View File
@@ -12,7 +12,7 @@ metadata:
{{- include "formbricks.labels" . | nindent 4 }}
data:
{{- if .Values.redis.enabled }}
REDIS_URL: {{ printf "redis://:%s@formbricks-redis-master:6379" $redisPassword | b64enc }}
REDIS_URL: {{ printf "redis://default:%s@formbricks-redis-master:6379" $redisPassword | b64enc }}
{{- else }}
REDIS_URL: {{ .Values.redis.externalRedisUrl | b64enc }}
{{- end }}
@@ -28,10 +28,13 @@ data:
ENTERPRISE_LICENSE_KEY: {{ .Values.enterprise.licenseKey | b64enc }}
{{- end }}
{{- if .Values.redis.enabled }}
REDIS_PASSWORD: {{ $redisPassword | b64enc }}
redis-password: {{ $redisPassword | b64enc }}
{{- end }}
{{- if .Values.postgresql.enabled }}
POSTGRES_ADMIN_PASSWORD: {{ $postgresAdminPassword | b64enc }}
POSTGRES_USER_PASSWORD: {{ $postgresUserPassword | b64enc }}
{{- end }}
{{- if .Values.minio.enabled }}
{{- end }}
{{- end }}
+28
View File
@@ -0,0 +1,28 @@
{{- if and (.Values.serviceMonitor).enabled (.Capabilities.APIVersions.Has "monitoring.coreos.com/v1") }}
---
apiVersion: "monitoring.coreos.com/v1"
kind: ServiceMonitor
metadata:
name: {{ template "formbricks.name" . }}-svc-monitor
namespace: {{ include "formbricks.namespace" . }}
labels:
{{- include "formbricks.labels" $ | nindent 4 }}
{{- if .Values.serviceMonitor.additionalLabels }}
{{ toYaml .Values.serviceMonitor.additionalLabels | indent 4 }}
{{- end }}
{{- if .Values.serviceMonitor.annotations }}
annotations:
{{- end }}
{{- if or .Values.serviceMonitor.annotations }}
{{ toYaml .Values.serviceMonitor.annotations | indent 4 }}
{{- end }}
spec:
selector:
matchLabels:
{{ include "formbricks.selectorLabels" $ | indent 6 }}
namespaceSelector:
matchNames:
- {{ include "formbricks.namespace" . }}
endpoints:
{{ toYaml .Values.serviceMonitor.endpoints | indent 4 }}
{{- end }}
+43 -2
View File
@@ -62,6 +62,8 @@ deployment:
env:
DOCKER_CRON_ENABLED:
value: "0"
PROMETHEUS_ENABLED:
value: "1"
# Tolerations for scheduling pods on tainted nodes
tolerations: []
@@ -236,7 +238,7 @@ redis:
auth:
enabled: true
existingSecret: "formbricks-app-secrets"
existingSecretPasswordKey: "REDIS_PASSWORD"
existingSecretPasswordKey: "redis-password"
networkPolicy:
enabled: false
master:
@@ -296,4 +298,43 @@ postgresql:
containerSecurityContext:
enabled: true
runAsUser: 1001
readOnlyRootFilesystem: false
readOnlyRootFilesystem: false
##########################################################
# Prometheus Rule Configuration
##########################################################
prometheusRule:
# -- (bool) Deploy a PrometheusRule (Prometheus Operator) resource.
# @section -- PrometheusRule Parameters
enabled: false
# -- (object) Additional labels for PrometheusRule.
# @section -- PrometheusRule Parameters
additionalLabels:
# prometheus: stakater-workload-monitoring
# role: alert-rules
# -- (list) Groups with alerting rules.
# Read more about it at [https://docs.openshift.com/container-platform/4.7/rest_api/monitoring_apis/prometheusrule-monitoring-coreos-com-v1.html](OpenShift's PrometheusRule documentation).
# @section -- PrometheusRule Parameters
groups:
- name: formbricks
rules:
- alert: AppDown
annotations:
message: >-
Not able to scrape formbricks, maybe app is Down (or not reachable)
expr: up{job="formbricks"} == 0
for: 1m
labels:
severity: critical
##########################################################
# Minio
##########################################################
minio:
enabled: true
fullnameOverride: formbricks-minio
mode: standalone
persistence:
enabled: true
size: 50Gi
defaultBuckets: "formbricks"
@@ -73,13 +73,17 @@ cronJob:
## Deployment & Autoscaling
deployment:
resources:
limits:
memory: 2Gi
requests:
cpu: 500m
memory: 512Mi
env:
DOCKER_CRON_ENABLED:
value: "0"
RATE_LIMITING_DISABLED:
value: "1"
S3_BUCKET_NAME:
value: {{ requiredEnv "FORMBRICKS_S3_BUCKET" }}
envFrom:
app-env:
nameSuffix: app-env
@@ -89,7 +93,7 @@ deployment:
reloadOnChange: true
autoscaling:
enabled: true
maxReplicas: 10
maxReplicas: 95
minReplicas: 3
metrics:
- resource:
@@ -162,3 +166,5 @@ postgresql:
enabled: false
redis:
enabled: false
minio:
enabled: false
+363
View File
@@ -0,0 +1,363 @@
#!/bin/env bash
set -e
ubuntu_version=$(lsb_release -a 2>/dev/null | grep -v "No LSB modules are available." | grep "Description:" | awk -F "Description:\t" '{print $2}')
install_formbricks() {
# Friendly welcome
echo "🧱 Welcome to the Formbricks Setup Script"
echo ""
echo "🛸 Fasten your seatbelts! We're setting up your Formbricks environment on your $ubuntu_version server with microK8s."
echo ""
# Update package list
echo "🔄 Updating your package list."
sudo apt-get update >/dev/null 2>&1
# Install dependencies
echo "📦 Installing the necessary dependencies."
sudo apt-get install -y \
ca-certificates \
curl \
gnupg \
lsb-release \
snapd >/dev/null 2>&1
# Install microK8s
echo "☸️ Installing microK8s Kubernetes..."
sudo snap install microk8s --classic >/dev/null 2>&1
# Add user to microk8s group
echo "👥 Adding your user to the microk8s group..."
sudo usermod -a -G microk8s $USER >/dev/null 2>&1
sudo mkdir -p ~/.kube >/dev/null 2>&1
sudo chown -R $USER ~/.kube >/dev/null 2>&1
# Create alias for kubectl
echo "🔧 Creating kubectl alias..."
sudo snap alias microk8s.kubectl kubectl >/dev/null 2>&1
# Wait for microk8s to be ready
echo "⏳ Waiting for microK8s to be ready..."
sudo microk8s status --wait-ready >/dev/null 2>&1
# Setting up microK8s configuration
mkdir -p ~/.kube
sudo microk8s config > ~/.kube/config
sudo chown -R $USER ~/.kube
# Enable required add-ons
echo "🔌 Enabling required microK8s add-ons (DNS, storage, ingress, helm3, cert-manager)..."
sudo microk8s enable dns storage ingress helm3 cert-manager >/dev/null 2>&1
echo "⏳ Waiting for add-ons to be ready..."
sleep 10
# Create formbricks directory
mkdir -p formbricks && cd formbricks
echo "📁 Created Formbricks directory at ./formbricks."
# Prompt for enabling Minio
echo "🪣 Do you want to enable Minio? [Y/n] (default is Y):"
read enable_minio
enable_minio=$(echo "$enable_minio" | tr '[:upper:]' '[:lower:]')
if [[ -z $enable_minio ]]; then
enable_minio="y"
fi
# Ask the user for their domain name
echo "🔗 Please enter your domain name for the SSL certificate (🚨 do NOT enter the protocol (http/https/etc)):"
read domain_name
echo "🔗 Please enter your Minio Console domain (Press Enter to use default: minio.${domain_name}):"
read minio_domain
minio_domain=${minio_domain:-minio.${domain_name}}
echo "🔗 Please enter your Minio API domain (Press Enter to use default: minio-api.${domain_name}):"
read minio_api_domain
minio_api_domain=${minio_api_domain:-minio-api.${domain_name}}
echo "🔗 Do you want us to set up an HTTPS certificate for you? [Y/n]"
read https_setup
https_setup=$(echo "$https_setup" | tr '[:upper:]' '[:lower:]')
# Set default value for HTTPS setup
if [[ -z $https_setup ]]; then
https_setup="y"
fi
if [[ $https_setup == "y" ]]; then
echo "🔗 Please make sure that the domain points to the server's IP address and that ports 80 & 443 are open in your server's firewall. Is everything set up? [Y/n]"
read dns_setup
dns_setup=$(echo "$dns_setup" | tr '[:upper:]' '[:lower:]')
# Set default value for DNS setup
if [[ -z $dns_setup ]]; then
dns_setup="y"
fi
if [[ $dns_setup == "y" ]]; then
echo "💡 Please enter your email address for the SSL certificate:"
read email_address
# Create ClusterIssuer for Let's Encrypt
echo "🔒 Creating Let's Encrypt certificate issuer..."
cat <<EOT > cluster-issuer.yaml
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
email: ${email_address}
server: https://acme-v02.api.letsencrypt.org/directory
privateKeySecretRef:
name: letsencrypt-prod-account-key
solvers:
- http01:
ingress:
ingressClassName: public
EOT
kubectl apply -f cluster-issuer.yaml
else
echo "❌ Ports 80 & 443 are not open. We can't help you in providing the SSL certificate."
https_setup="n"
fi
else
https_setup="n"
fi
# Prompt for email service setup
read -p "📧 Do you want to set up the email service? You will need SMTP credentials for the same! [y/N]" email_service
email_service=$(echo "$email_service" | tr '[:upper:]' '[:lower:]')
# Set default value for email service setup
if [[ -z $email_service ]]; then
email_service="n"
fi
# Create values file for Helm chart
echo "📝 Creating values file for Helm chart..."
cat <<EOT > values.yaml
# Formbricks helm chart values
deployment:
env:
NEXTAUTH_URL:
value: "https://${domain_name}"
WEBAPP_URL:
value: "https://${domain_name}"
DOCKER_CRON_ENABLED:
value: "0"
EOT
# Add email configuration if selected
if [[ $email_service == "y" ]]; then
echo -n "Enter your SMTP configured Email ID: "
read mail_from
echo -n "Enter your SMTP configured Email Name: "
read mail_from_name
echo -n "Enter your SMTP Host URL: "
read smtp_host
echo -n "Enter your SMTP Host Port: "
read smtp_port
echo -n "Enter your SMTP username: "
read smtp_user
echo -n "Enter your SMTP password: "
read smtp_password
echo -n "Enable Authenticated SMTP? Enter 1 for yes and 0 for no(default is 1): "
read smtp_authenticated
echo -n "Enable Secure SMTP (use SSL)? Enter 1 for yes and 0 for no: "
read smtp_secure_enabled
# Add SMTP configuration to values.yaml
cat <<EOT >> values.yaml
MAIL_FROM:
value: "${mail_from}"
MAIL_FROM_NAME:
value: "${mail_from_name}"
SMTP_HOST:
value: "${smtp_host}"
SMTP_PORT:
value: "${smtp_port}"
SMTP_USER:
value: "${smtp_user}"
SMTP_PASSWORD:
value: "${smtp_password}"
SMTP_AUTHENTICATED:
value: ${smtp_authenticated:-1}
SMTP_SECURE_ENABLED:
value: ${smtp_secure_enabled:-0}
EOT
else
cat <<EOT >> values.yaml
EMAIL_VERIFICATION_DISABLED:
value: "1"
PASSWORD_RESET_DISABLED:
value: "1"
EOT
fi
if [[ $enable_minio == "y" ]]; then
cat <<EOT >> values.yaml
S3_ENDPOINT_URL:
value: "https://${minio_api_domain}"
EOT
fi
# Configure ingress with SSL
if [[ $https_setup == "y" ]]; then
cat <<EOT >> values.yaml
ingress:
enabled: true
ingressClassName: public
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
kubernetes.io/tls-acme: "true"
hosts:
- host: ${domain_name}
paths:
- path: /
pathType: Prefix
tls:
- secretName: formbricks-tls
hosts:
- ${domain_name}
EOT
else
cat <<EOT >> values.yaml
ingress:
enabled: true
ingressClassName: public
hosts:
- host: ${domain_name}
paths:
- path: /
pathType: Prefix
EOT
fi
# Configure ingress for Minio
if [[ $enable_minio == "y" ]]; then
cat <<EOT >> values.yaml
minio:
ingress:
enabled: true
ingressClassName: public
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
kubernetes.io/tls-acme: "true"
hostname: ${minio_domain}
tls: true
apiIngress:
enabled: true
ingressClassName: public
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
kubernetes.io/tls-acme: "true"
hostname: ${minio_api_domain}
extraHosts:
- name: formbricks.${minio_api_domain}
path: /
tls: true
EOT
fi
# Create a namespace for Formbricks
echo "🚀 Ensuring namespace 'formbricks' exists..."
if ! kubectl get namespace formbricks >/dev/null 2>&1; then
kubectl create namespace formbricks
echo "✅ Namespace 'formbricks' created."
else
echo "️ Namespace 'formbricks' already exists."
fi
# Add helm repo and update
# echo "⚓ Adding Formbricks Helm repository..."
# microk8s helm3 repo add formbricks-repo oci://ghcr.io/formbricks/helm-charts
# microk8s helm3 repo update
# Install Formbricks with Helm
echo "🚀 Installing Formbricks via Helm chart..."
microk8s helm3 upgrade -i formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks --create-namespace -f values.yaml
echo "⏳ Waiting for Formbricks to be ready..."
kubectl -n formbricks rollout status deployment formbricks
echo "🚨 Make sure you have set up the DNS records for ${domain_name} pointing to this server's IP address."
echo ""
echo "🎉 All done! Please setup your Formbricks instance by visiting your domain at https://${domain_name}."
echo "You can check the status of your deployment with 'kubectl get all -n formbricks'"
}
uninstall_formbricks() {
echo "🗑️ Preparing to Uninstall Formbricks..."
read -p "Are you sure you want to uninstall Formbricks? This will delete all the data associated with it! (yes/no): " uninstall_confirmation
uninstall_confirmation=$(echo "$uninstall_confirmation" | tr '[:upper:]' '[:lower:]')
if [[ $uninstall_confirmation == "yes" ]]; then
cd formbricks
microk8s helm3 uninstall formbricks -n formbricks
kubectl delete namespace formbricks
echo "🛑 Formbricks uninstalled successfully!"
else
echo "❌ Uninstalling Formbricks has been cancelled."
fi
}
stop_formbricks() {
echo "🛑 Stopping Formbricks..."
kubectl scale deployment formbricks --replicas=0 -n formbricks
echo "🎉 Formbricks instance scaled down to zero successfully!"
}
update_formbricks() {
echo "🔄 Updating Formbricks..."
cd formbricks
microk8s helm3 upgrade formbricks oci://ghcr.io/formbricks/helm-charts/formbricks -n formbricks -f values.yaml
echo "🎉 Formbricks updated successfully!"
echo "🎉 Check the status of Formbricks with 'kubectl get pods -n formbricks'"
}
restart_formbricks() {
echo "🔄 Restarting Formbricks..."
kubectl rollout restart deployment formbricks -n formbricks
echo "🎉 Formbricks restarted successfully!"
}
get_logs() {
echo "📃 Getting Formbricks logs..."
kubectl logs -l app.kubernetes.io/name=formbricks -n formbricks --tail=100
}
case "$1" in
install)
install_formbricks
;;
update)
update_formbricks
;;
stop)
stop_formbricks
;;
restart)
restart_formbricks
;;
logs)
get_logs
;;
uninstall)
uninstall_formbricks
;;
*)
echo "🚀 Executing default step of installing Formbricks"
install_formbricks
;;
esac
+73 -16
View File
@@ -27,50 +27,63 @@ module "cloudwatch_cis-alarms" {
}
locals {
alb_id = "app/k8s-formbricks-21ab9ecd60/342ed65d128ce4cb"
alarms = {
ALB_HTTPCode_Target_5XX_Count = {
alarm_description = "Average API 5XX target group error code count is too high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 5
threshold = 1
period = 60
threshold = 5
period = 600
unit = "Count"
namespace = "AWS/ApplicationELB"
metric_name = "HTTPCode_Target_5XX_Count"
statistic = "Sum"
dimensions = {
LoadBalancer = local.alb_id
}
}
ALB_HTTPCode_ELB_5XX_Count = {
alarm_description = "Average API 5XX load balancer error code count is too high"
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 5
threshold = 1
period = 60
threshold = 5
period = 600
unit = "Count"
namespace = "AWS/ApplicationELB"
metric_name = "HTTPCode_ELB_5XX_Count"
statistic = "Sum"
dimensions = {
LoadBalancer = local.alb_id
}
}
ALB_TargetResponseTime = {
alarm_description = format("Average API response time is greater than %s", 0.05)
alarm_description = format("Average API response time is greater than %s", 5)
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 5
threshold = 0.05
threshold = 5
period = 60
unit = "Seconds"
namespace = "AWS/ApplicationELB"
metric_name = "TargetResponseTime"
statistic = "Average"
dimensions = {
LoadBalancer = local.alb_id
}
}
ALB_UnHealthyHostCount = {
alarm_description = format("Unhealthy host count is greater than %s", 1)
alarm_description = format("Unhealthy host count is greater than %s", 2)
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 5
threshold = 1
threshold = 2
period = 60
unit = "Count"
namespace = "AWS/ApplicationELB"
metric_name = "UnHealthyHostCount"
statistic = "Minimum"
dimensions = {
LoadBalancer = local.alb_id
}
}
RDS_CPUUtilization = {
alarm_description = format("Average RDS CPU utilization is greater than %s", 80)
@@ -82,6 +95,9 @@ locals {
namespace = "AWS/RDS"
metric_name = "CPUUtilization"
statistic = "Average"
dimensions = {
DBInstanceIdentifier = module.rds-aurora.cluster_instances["one"].id
}
}
RDS_FreeStorageSpace = {
alarm_description = format("Average RDS free storage space is less than %s", 5)
@@ -93,6 +109,9 @@ locals {
namespace = "AWS/RDS"
metric_name = "FreeStorageSpace"
statistic = "Average"
dimensions = {
DBInstanceIdentifier = module.rds-aurora.cluster_instances["one"].id
}
}
RDS_FreeableMemory = {
alarm_description = format("Average RDS freeable memory is less than %s", 100)
@@ -104,6 +123,9 @@ locals {
namespace = "AWS/RDS"
metric_name = "FreeableMemory"
statistic = "Average"
dimensions = {
DBInstanceIdentifier = module.rds-aurora.cluster_instances["one"].id
}
}
RDS_DiskQueueDepth = {
alarm_description = format("Average RDS disk queue depth is greater than %s", 1)
@@ -115,6 +137,9 @@ locals {
namespace = "AWS/RDS"
metric_name = "DiskQueueDepth"
statistic = "Average"
dimensions = {
DBInstanceIdentifier = module.rds-aurora.cluster_instances["one"].id
}
}
RDS_ReadIOPS = {
alarm_description = format("Average RDS read IOPS is greater than %s", 1000)
@@ -126,6 +151,9 @@ locals {
namespace = "AWS/RDS"
metric_name = "ReadIOPS"
statistic = "Average"
dimensions = {
DBInstanceIdentifier = module.rds-aurora.cluster_instances["one"].id
}
}
RDS_WriteIOPS = {
alarm_description = format("Average RDS write IOPS is greater than %s", 1000)
@@ -137,6 +165,9 @@ locals {
namespace = "AWS/RDS"
metric_name = "WriteIOPS"
statistic = "Average"
dimensions = {
DBInstanceIdentifier = module.rds-aurora.cluster_instances["one"].id
}
}
SQS_ApproximateAgeOfOldestMessage = {
alarm_description = format("Average SQS approximate age of oldest message is greater than %s", 300)
@@ -148,6 +179,9 @@ locals {
namespace = "AWS/SQS"
metric_name = "ApproximateAgeOfOldestMessage"
statistic = "Maximum"
dimensions = {
QueueName = module.karpenter.queue_name
}
}
DynamoDB_ConsumedReadCapacityUnits = {
alarm_description = format("Average DynamoDB consumed read capacity units is greater than %s", 90)
@@ -159,6 +193,23 @@ locals {
namespace = "AWS/DynamoDB"
metric_name = "ConsumedReadCapacityUnits"
statistic = "Average"
dimensions = {
TableName = "terraform-lock"
}
}
DynamoDB_ConsumedWriteCapacityUnits = {
alarm_description = format("Average DynamoDB consumed write capacity units is greater than %s", 90)
comparison_operator = "GreaterThanThreshold"
evaluation_periods = 5
threshold = 90
period = 60
unit = "Count"
namespace = "AWS/DynamoDB"
metric_name = "ConsumedWriteCapacityUnits"
statistic = "Average"
dimensions = {
TableName = "terraform-lock"
}
}
Lambda_Errors = {
alarm_description = format("Average Lambda errors is greater than %s", 1)
@@ -170,6 +221,9 @@ locals {
namespace = "AWS/Lambda"
metric_name = "Errors"
statistic = "Sum"
dimensions = {
FunctionName = module.notify-slack.notify_slack_lambda_function_name
}
}
}
}
@@ -178,18 +232,21 @@ module "metric_alarm" {
source = "terraform-aws-modules/cloudwatch/aws//modules/metric-alarm"
version = "5.7.1"
for_each = local.alarms
alarm_name = each.key
alarm_description = each.value.alarm_description
comparison_operator = each.value.comparison_operator
evaluation_periods = each.value.evaluation_periods
threshold = each.value.threshold
period = each.value.period
unit = each.value.unit
for_each = local.alarms
alarm_name = each.key
alarm_description = each.value.alarm_description
comparison_operator = each.value.comparison_operator
evaluation_periods = each.value.evaluation_periods
threshold = each.value.threshold
period = each.value.period
unit = each.value.unit
insufficient_data_actions = []
namespace = each.value.namespace
metric_name = each.value.metric_name
statistic = each.value.statistic
dimensions = each.value.dimensions
alarm_actions = [module.notify-slack.slack_topic_arn]
}
+61 -11
View File
@@ -1,6 +1,10 @@
################################################################################
# ElastiCache Module
################################################################################
locals {
valkey_major_version = 8
}
resource "random_password" "valkey" {
length = 20
special = false
@@ -46,21 +50,67 @@ module "elasticache_user_group" {
})
}
module "valkey" {
source = "terraform-aws-modules/elasticache/aws"
version = "1.4.1"
replication_group_id = "${local.name}-valkey"
engine = "valkey"
engine_version = "8.0"
node_type = "cache.m7g.large"
transit_encryption_enabled = true
auth_token = random_password.valkey.result
maintenance_window = "sun:05:00-sun:09:00"
apply_immediately = true
# Security Group
vpc_id = module.vpc.vpc_id
security_group_rules = {
ingress_vpc = {
# Default type is `ingress`
# Default port is based on the default engine port
description = "VPC traffic"
cidr_ipv4 = module.vpc.vpc_cidr_block
}
}
log_delivery_configuration = {
slow-log = {
destination_type = "cloudwatch-logs"
log_format = "json"
cloudwatch_log_group_retention_in_days = 365
}
}
# Subnet Group
subnet_group_name = "${local.name}-valkey"
subnet_group_description = "${title(local.name)} subnet group"
subnet_ids = module.vpc.database_subnets
# Parameter Group
create_parameter_group = true
parameter_group_name = "${local.name}-valkey-${local.valkey_major_version}"
parameter_group_family = "valkey8"
parameter_group_description = "${title(local.name)} parameter group"
parameters = [
{
name = "latency-tracking"
value = "yes"
}
]
tags = local.tags
}
module "valkey_serverless" {
source = "terraform-aws-modules/elasticache/aws//modules/serverless-cache"
version = "1.4.1"
engine = "valkey"
cache_name = "${local.name}-valkey-serverless"
cache_usage_limits = {
data_storage = {
maximum = 2
}
ecpu_per_second = {
maximum = 1000
}
}
major_engine_version = 7
engine = "valkey"
cache_name = "${local.name}-valkey-serverless"
major_engine_version = 8
subnet_ids = module.vpc.database_subnets
security_group_ids = [
+57 -155
View File
@@ -120,6 +120,7 @@ module "eks" {
enable_cluster_creator_admin_permissions = false
cluster_endpoint_public_access = true
cloudwatch_log_group_retention_in_days = 365
cluster_addons = {
coredns = {
@@ -352,7 +353,7 @@ resource "kubernetes_manifest" "node_pool" {
}
}
limits = {
cpu = 100
cpu = 1000
}
disruption = {
consolidationPolicy = "WhenEmpty"
@@ -412,19 +413,65 @@ module "eks_blueprints_addons" {
}
### Formbricks App
module "s3-bucket" {
data "aws_iam_policy_document" "replication_bucket_policy" {
statement {
sid = "Set-permissions-for-objects"
effect = "Allow"
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::050559574035:role/service-role/s3crr_role_for_formbricks-cloud-uploads"
]
}
actions = [
"s3:ReplicateObject",
"s3:ReplicateDelete"
]
resources = [
"arn:aws:s3:::formbricks-cloud-eks/*"
]
}
statement {
sid = "Set permissions on bucket"
effect = "Allow"
principals {
type = "AWS"
identifiers = [
"arn:aws:iam::050559574035:role/service-role/s3crr_role_for_formbricks-cloud-uploads"
]
}
actions = [
"s3:GetBucketVersioning",
"s3:PutBucketVersioning"
]
resources = [
"arn:aws:s3:::formbricks-cloud-eks"
]
}
}
module "formbricks_s3_bucket" {
source = "terraform-aws-modules/s3-bucket/aws"
version = "4.6.0"
bucket_prefix = "formbricks-"
bucket = "formbricks-cloud-eks"
force_destroy = true
control_object_ownership = true
object_ownership = "BucketOwnerPreferred"
versioning = {
enabled = true
}
policy = data.aws_iam_policy_document.replication_bucket_policy.json
}
module "iam_policy" {
module "formbricks_app_iam_policy" {
source = "terraform-aws-modules/iam/aws//modules/iam-policy"
version = "5.53.0"
@@ -441,8 +488,8 @@ module "iam_policy" {
"s3:*",
]
Resource = [
module.s3-bucket.s3_bucket_arn,
"${module.s3-bucket.s3_bucket_arn}/*",
module.formbricks_s3_bucket.s3_bucket_arn,
"${module.formbricks_s3_bucket.s3_bucket_arn}/*",
"arn:aws:s3:::formbricks-cloud-uploads",
"arn:aws:s3:::formbricks-cloud-uploads/*"
]
@@ -451,14 +498,14 @@ module "iam_policy" {
})
}
module "formkey-aws-access" {
module "formbricks_app_iam_role" {
source = "terraform-aws-modules/iam/aws//modules/iam-role-for-service-accounts-eks"
version = "5.53.0"
role_name_prefix = "formbricks-"
role_policy_arns = {
"formbricks" = module.iam_policy.arn
"formbricks" = module.formbricks_app_iam_policy.arn
}
assume_role_condition_test = "StringLike"
@@ -469,148 +516,3 @@ module "formkey-aws-access" {
}
}
}
resource "helm_release" "formbricks" {
name = "formbricks"
namespace = "formbricks"
repository = "oci://ghcr.io/formbricks/helm-charts"
chart = "formbricks"
version = "3.5.1"
max_history = 5
values = [
<<-EOT
postgresql:
enabled: false
redis:
enabled: false
ingress:
enabled: true
ingressClassName: alb
hosts:
- host: "app.${local.domain}"
paths:
- path: /
pathType: "Prefix"
serviceName: "formbricks"
annotations:
alb.ingress.kubernetes.io/scheme: internet-facing
alb.ingress.kubernetes.io/target-type: ip
alb.ingress.kubernetes.io/listen-ports: '[{"HTTP": 80}, {"HTTPS": 443}]'
alb.ingress.kubernetes.io/ssl-redirect: "443"
alb.ingress.kubernetes.io/certificate-arn: ${data.aws_acm_certificate.formbricks.arn}
alb.ingress.kubernetes.io/healthcheck-path: "/health"
alb.ingress.kubernetes.io/group.name: formbricks
alb.ingress.kubernetes.io/ssl-policy: "ELBSecurityPolicy-TLS13-1-2-2021-06"
secret:
enabled: false
rbac:
enabled: true
serviceAccount:
enabled: true
name: formbricks
annotations:
eks.amazonaws.com/role-arn: ${module.formkey-aws-access.iam_role_arn}
serviceMonitor:
enabled: true
deployment:
reloadOnChange: true
nodeSelector:
karpenter.sh/capacity-type: "on-demand"
env:
S3_BUCKET_NAME:
value: "formbricks-cloud-uploads"
RATE_LIMITING_DISABLED:
value: "1"
envFrom:
app-env:
type: secret
nameSuffix: app-env
externalSecret:
enabled: true # Enable/disable ExternalSecrets
secretStore:
name: aws-secrets-manager
kind: ClusterSecretStore
refreshInterval: "1m"
files:
app-env:
dataFrom:
key: "prod/formbricks/environment"
app-secrets:
dataFrom:
key: "prod/formbricks/secrets"
cronJob:
enabled: true
jobs:
survey-status:
schedule: "0 0 * * *"
successfulJobsHistoryLimit: 0
env:
CRON_SECRET:
valueFrom:
secretKeyRef:
name: "formbricks-app-env"
key: "CRON_SECRET"
WEBAPP_URL:
valueFrom:
secretKeyRef:
name: "formbricks-app-env"
key: "WEBAPP_URL"
image:
repository: curlimages/curl
tag: latest
imagePullPolicy: IfNotPresent
args:
- "/bin/sh"
- "-c"
- 'curl -X POST -H "content-type: application/json" -H "x-api-key: $CRON_SECRET" "$WEBAPP_URL/api/cron/survey-status"'
weekely-summary:
schedule: "0 8 * * 1"
successfulJobsHistoryLimit: 0
env:
CRON_SECRET:
valueFrom:
secretKeyRef:
name: "formbricks-app-env"
key: "CRON_SECRET"
WEBAPP_URL:
valueFrom:
secretKeyRef:
name: "formbricks-app-env"
key: "WEBAPP_URL"
image:
repository: curlimages/curl
tag: latest
imagePullPolicy: IfNotPresent
args:
- "/bin/sh"
- "-c"
- 'curl -X POST -H "content-type: application/json" -H "x-api-key: $CRON_SECRET" "$WEBAPP_URL/api/cron/weekly-summary"'
ping:
schedule: "0 9 * * *"
successfulJobsHistoryLimit: 0
env:
CRON_SECRET:
valueFrom:
secretKeyRef:
name: "formbricks-app-env"
key: "CRON_SECRET"
WEBAPP_URL:
valueFrom:
secretKeyRef:
name: "formbricks-app-env"
key: "WEBAPP_URL"
image:
repository: curlimages/curl
tag: latest
imagePullPolicy: IfNotPresent
args:
- "/bin/sh"
- "-c"
- 'curl -X POST -H "content-type: application/json" -H "x-api-key: $CRON_SECRET" "$WEBAPP_URL/api/cron/ping"'
EOT
]
}
# secrets password/keys
+10 -9
View File
@@ -15,14 +15,14 @@ module "rds-aurora" {
source = "terraform-aws-modules/rds-aurora/aws"
version = "9.12.0"
name = "${local.name}-postgres"
engine = data.aws_rds_engine_version.postgresql.engine
engine_mode = "provisioned"
engine_version = data.aws_rds_engine_version.postgresql.version
storage_encrypted = true
master_username = "formbricks"
master_password = random_password.postgres.result
manage_master_user_password = false
name = "${local.name}-postgres"
engine = data.aws_rds_engine_version.postgresql.engine
engine_mode = "provisioned"
engine_version = data.aws_rds_engine_version.postgresql.version
storage_encrypted = true
master_username = "formbricks"
master_password = random_password.postgres.result
manage_master_user_password = false
create_db_cluster_parameter_group = true
db_cluster_parameter_group_family = data.aws_rds_engine_version.postgresql.parameter_group_family
db_cluster_parameter_group_parameters = [
@@ -40,7 +40,8 @@ module "rds-aurora" {
cidr_blocks = module.vpc.private_subnets_cidr_blocks
}
}
performance_insights_enabled = true
performance_insights_enabled = true
cluster_performance_insights_enabled = true
apply_immediately = true
skip_final_snapshot = true
+15 -2
View File
@@ -3,10 +3,23 @@ resource "aws_secretsmanager_secret" "formbricks_app_secrets" {
name = "prod/formbricks/secrets"
}
resource "aws_secretsmanager_secret" "formbricks_app_secrets_temp" {
name = "prod/formbricks/secrets_temp"
}
resource "aws_secretsmanager_secret_version" "formbricks_app_secrets" {
secret_id = aws_secretsmanager_secret.formbricks_app_secrets.id
secret_string = jsonencode({
# DATABASE_URL = "postgres://formbricks:${random_password.postgres.result}@${module.rds-aurora.cluster_endpoint}/formbricks"
REDIS_URL = "rediss://formbricks:${random_password.valkey.result}@${module.valkey_serverless.serverless_cache_endpoint[0].address}:6379"
# DATABASE_URL = "postgres://formbricks:${random_password.postgres.result}@${module.rds-aurora.cluster_endpoint}/formbricks"
REDIS_URL = "rediss://:${random_password.valkey.result}@${module.valkey.replication_group_primary_endpoint_address}:6379"
# REDIS_URL = "rediss://formbricks:${random_password.valkey.result}@${module.valkey_serverless.serverless_cache_endpoint[0].address}:6379"
})
}
resource "aws_secretsmanager_secret_version" "formbricks_app_secrets_temp" {
secret_id = aws_secretsmanager_secret.formbricks_app_secrets_temp.id
secret_string = jsonencode({
DATABASE_URL = "postgres://formbricks:${random_password.postgres.result}@${module.rds-aurora.cluster_endpoint}/formbricks"
# REDIS_URL = "rediss://formbricks:${random_password.valkey.result}@${module.valkey_serverless.serverless_cache_endpoint[0].address}:6379"
})
}
+7 -6
View File
@@ -1,6 +1,6 @@
{
"name": "@formbricks/react-native",
"version": "2.1.0",
"version": "2.1.1",
"license": "MIT",
"description": "Formbricks React Native SDK allows you to connect your app to Formbricks, display surveys and trigger events.",
"homepage": "https://formbricks.com",
@@ -41,24 +41,25 @@
"coverage": "vitest run --coverage"
},
"dependencies": {
"@react-native-community/netinfo": "11.4.1",
"zod": "3.24.1"
},
"devDependencies": {
"@formbricks/api": "workspace:*",
"@formbricks/config-typescript": "workspace:*",
"@types/react": "18.3.1",
"@vitest/coverage-v8": "3.0.4",
"react": "18.3.1",
"react-native": "0.74.5",
"terser": "5.37.0",
"vite": "6.0.12",
"vite-plugin-dts": "4.3.0",
"vitest": "3.0.5",
"react": "18.3.1",
"@types/react": "18.3.1"
"vitest": "3.0.5"
},
"peerDependencies": {
"@react-native-async-storage/async-storage": ">=2.1.0",
"react": ">=16.8.0",
"react-native": ">=0.60.0",
"react-native-webview": ">=13.0.0",
"@react-native-async-storage/async-storage": ">=2.1.0"
"react-native-webview": ">=13.0.0"
}
}
+40 -14
View File
@@ -1,3 +1,4 @@
import { fetch } from "@react-native-community/netinfo";
import { RNConfig } from "@/lib/common/config";
import { Logger } from "@/lib/common/logger";
import { shouldDisplayBasedOnPercentage } from "@/lib/common/utils";
@@ -62,24 +63,49 @@ export const trackAction = (name: string, alias?: string): Result<void, NetworkE
* @param code - The action code to track
* @returns Result indicating success, network error, or invalid code error
*/
export const track = (code: string): Result<void, NetworkError> | Result<void, InvalidCodeError> => {
const appConfig = RNConfig.getInstance();
export const track = async (
code: string
): Promise<
| Result<void, NetworkError>
| Result<void, InvalidCodeError>
| Result<void, { code: "error"; message: string }>
> => {
try {
const appConfig = RNConfig.getInstance();
const {
environment: {
data: { actionClasses = [] },
},
} = appConfig.get();
const netInfo = await fetch();
const codeActionClasses = actionClasses.filter((action) => action.type === "code");
const actionClass = codeActionClasses.find((action) => action.key === code);
if (!netInfo.isConnected) {
return err({
code: "network_error",
status: 500,
message: "No internet connection. Please check your connection and try again.",
responseMessage: "No internet connection. Please check your connection and try again.",
url: new URL(`${appConfig.get().appUrl}/js/surveys.umd.cjs`),
});
}
if (!actionClass) {
const {
environment: {
data: { actionClasses = [] },
},
} = appConfig.get();
const codeActionClasses = actionClasses.filter((action) => action.type === "code");
const actionClass = codeActionClasses.find((action) => action.key === code);
if (!actionClass) {
return err({
code: "invalid_code",
message: `${code} action unknown. Please add this action in Formbricks first in order to use it in your code.`,
});
}
return trackAction(actionClass.name, code);
} catch (error) {
return err({
code: "invalid_code",
message: `${code} action unknown. Please add this action in Formbricks first in order to use it in your code.`,
code: "error",
message: "Error tracking action",
});
}
return trackAction(actionClass.name, code);
};
@@ -36,6 +36,12 @@ vi.mock("@/lib/common/utils", () => ({
shouldDisplayBasedOnPercentage: vi.fn(),
}));
vi.mock("@react-native-community/netinfo", () => ({
fetch: vi.fn(() => ({
isConnected: true,
})),
}));
describe("survey/action.ts", () => {
const mockSurvey = {
id: "survey_001",
@@ -153,15 +159,14 @@ describe("survey/action.ts", () => {
});
});
test("tracks a valid action by code", () => {
const result = track("testCode");
test("tracks a valid action by code", async () => {
const result = await track("testCode");
expect(result.ok).toBe(true);
// expect(mockLogger.debug).toHaveBeenCalledWith('Formbricks: Action "testAction" tracked');
});
test("returns error for invalid action code", () => {
const result = track("invalidCode");
test("returns error for invalid action code", async () => {
const result = await track("invalidCode");
expect(result.ok).toBe(false);
+879 -1166
View File
File diff suppressed because it is too large Load Diff