mirror of
https://github.com/formbricks/formbricks.git
synced 2025-12-21 13:40:31 -06:00
Compare commits
22 Commits
4.0.0
...
configurab
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f826d578c8 | ||
|
|
3359c50053 | ||
|
|
4357086399 | ||
|
|
49edfc51a5 | ||
|
|
1d98f2e45d | ||
|
|
60b5dfd810 | ||
|
|
12540e0bc6 | ||
|
|
3762eed198 | ||
|
|
6acc1fdb51 | ||
|
|
f856b8cd93 | ||
|
|
75b3b13029 | ||
|
|
23c268009d | ||
|
|
b758769730 | ||
|
|
365af5495b | ||
|
|
1abaddcdb5 | ||
|
|
99d05c93cd | ||
|
|
96d5731d33 | ||
|
|
d45ee265b0 | ||
|
|
ed663e7826 | ||
|
|
72aa73f209 | ||
|
|
946c6f5e9b | ||
|
|
23706a935d |
@@ -212,4 +212,10 @@ UNKEY_ROOT_KEY=
|
||||
# SENTRY_AUTH_TOKEN=
|
||||
|
||||
# Disable the user management from UI
|
||||
# DISABLE_USER_MANAGEMENT=1
|
||||
# DISABLE_USER_MANAGEMENT=1
|
||||
|
||||
# Configure the initial user and organization to be created on startup.
|
||||
# INITIAL_USER_EMAIL=
|
||||
# INITIAL_USER_PASSWORD=
|
||||
# INITIAL_ORGANIZATION_NAME=
|
||||
# INITIAL_PROJECT_NAME=
|
||||
@@ -142,6 +142,12 @@ RUN chmod -R 755 ./node_modules/@noble/hashes
|
||||
COPY --from=installer /app/node_modules/zod ./node_modules/zod
|
||||
RUN chmod -R 755 ./node_modules/zod
|
||||
|
||||
COPY --from=installer /app/node_modules/bcryptjs ./node_modules/bcryptjs
|
||||
RUN chmod -R 755 ./node_modules/bcryptjs
|
||||
|
||||
COPY --from=installer /app/packages/database/zod ./packages/database/zod
|
||||
RUN chown -R nextjs:nextjs ./packages/database/zod && chmod -R 755 ./packages/database/zod
|
||||
|
||||
RUN npm install --ignore-scripts -g tsx typescript pino-pretty
|
||||
RUN npm install -g prisma
|
||||
|
||||
@@ -166,4 +172,6 @@ CMD if [ "${DOCKER_CRON_ENABLED:-1}" = "1" ]; then \
|
||||
fi; \
|
||||
(cd packages/database && npm run db:migrate:deploy) && \
|
||||
(cd packages/database && npm run db:create-saml-database:deploy) && \
|
||||
(cd packages/database && npm run db:initial-user-setup:deploy) && \
|
||||
|
||||
exec node apps/web/server.js
|
||||
@@ -283,3 +283,9 @@ export const SENTRY_DSN = env.SENTRY_DSN;
|
||||
export const PROMETHEUS_ENABLED = env.PROMETHEUS_ENABLED === "1";
|
||||
|
||||
export const DISABLE_USER_MANAGEMENT = env.DISABLE_USER_MANAGEMENT === "1";
|
||||
|
||||
//initial setup variables
|
||||
export const INITIAL_USER_EMAIL = env.INITIAL_USER_EMAIL;
|
||||
export const INITIAL_USER_PASSWORD = env.INITIAL_USER_PASSWORD;
|
||||
export const INITIAL_ORGANIZATION_NAME = env.INITIAL_ORGANIZATION_NAME;
|
||||
export const INITIAL_PROJECT_NAME = env.INITIAL_PROJECT_NAME;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { createEnv } from "@t3-oss/env-nextjs";
|
||||
import { z } from "zod";
|
||||
import { ZUserEmail, ZUserPassword } from "@formbricks/types/user";
|
||||
|
||||
export const env = createEnv({
|
||||
/*
|
||||
@@ -105,6 +106,10 @@ export const env = createEnv({
|
||||
PROMETHEUS_EXPORTER_PORT: z.string().optional(),
|
||||
PROMETHEUS_ENABLED: z.enum(["1", "0"]).optional(),
|
||||
DISABLE_USER_MANAGEMENT: z.enum(["1", "0"]).optional(),
|
||||
INITIAL_USER_EMAIL: ZUserEmail.optional(),
|
||||
INITIAL_USER_PASSWORD: ZUserPassword.optional(),
|
||||
INITIAL_ORGANIZATION_NAME: z.string().optional(),
|
||||
INITIAL_PROJECT_NAME: z.string().optional(),
|
||||
},
|
||||
|
||||
/*
|
||||
@@ -200,5 +205,9 @@ export const env = createEnv({
|
||||
PROMETHEUS_ENABLED: process.env.PROMETHEUS_ENABLED,
|
||||
PROMETHEUS_EXPORTER_PORT: process.env.PROMETHEUS_EXPORTER_PORT,
|
||||
DISABLE_USER_MANAGEMENT: process.env.DISABLE_USER_MANAGEMENT,
|
||||
INITIAL_USER_EMAIL: process.env.INITIAL_USER_EMAIL,
|
||||
INITIAL_USER_PASSWORD: process.env.INITIAL_USER_PASSWORD,
|
||||
INITIAL_ORGANIZATION_NAME: process.env.INITIAL_ORGANIZATION_NAME,
|
||||
INITIAL_PROJECT_NAME: process.env.INITIAL_PROJECT_NAME,
|
||||
},
|
||||
});
|
||||
|
||||
@@ -29,6 +29,14 @@ x-environment: &environment
|
||||
# Set the minimum log level(debug, info, warn, error, fatal)
|
||||
# LOG_LEVEL: info
|
||||
|
||||
############################################# OPTIONAL (AUTOMATIC SETUP) #############################################
|
||||
|
||||
# Set these to automatically create the initial admin user on first startup
|
||||
# INITIAL_USER_EMAIL: admin@example.com
|
||||
# INITIAL_USER_PASSWORD: your-secure-password
|
||||
# INITIAL_ORGANIZATION_NAME: My Organization
|
||||
# INITIAL_PROJECT_NAME: My First Project
|
||||
|
||||
############################################# OPTIONAL (ENTERPRISE EDITION) #############################################
|
||||
|
||||
# Enterprise License Key (More info at: https://formbricks.com/docs/self-hosting/license)
|
||||
|
||||
85
docs/self-hosting/configuration/automated-setup.mdx
Normal file
85
docs/self-hosting/configuration/automated-setup.mdx
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: "Automated Setup"
|
||||
description: "Automatically create the first user and organization with environment variables"
|
||||
icon: "robot"
|
||||
---
|
||||
|
||||
# Automated Setup
|
||||
|
||||
When deploying Formbricks in production environments, it's often useful to automate the initial setup process. This guide explains how to use environment variables to automatically create the first user, organization, and project during startup.
|
||||
|
||||
## Overview
|
||||
|
||||
Formbricks supports automatic creation of the initial administrator user on first startup through environment variables. This is particularly useful for:
|
||||
|
||||
- Automating deployments of multiple instances
|
||||
- Setting up Formbricks in Docker or Kubernetes environments
|
||||
- Integrating with infrastructure as code tools
|
||||
- Streamlining CI/CD pipelines
|
||||
|
||||
## Configuration
|
||||
|
||||
To enable automated setup, set the following environment variables before starting Formbricks:
|
||||
|
||||
### Required Variables
|
||||
|
||||
| Variable | Description | Example |
|
||||
| --- | --- | --- |
|
||||
| `INITIAL_USER_EMAIL` | The email address for the admin user | `admin@example.com` |
|
||||
| `INITIAL_USER_PASSWORD` | The password for the admin user | `your-secure-password` |
|
||||
|
||||
### Optional Variables
|
||||
|
||||
| Variable | Description | Default Value | Example |
|
||||
| --- | --- | --- | --- |
|
||||
| `INITIAL_ORGANIZATION_NAME` | The name of the initial organization | `My Organization` | `Acme Corp` |
|
||||
| `INITIAL_PROJECT_NAME` | The name of the initial project | *none* | `Customer Feedback` |
|
||||
|
||||
## How It Works
|
||||
|
||||
When Formbricks starts up, it checks if these environment variables are defined and if the database is empty (no users exist). If both conditions are met, it will:
|
||||
|
||||
1. Create an administrator user with the provided email and password
|
||||
2. Create an organization with the provided or default name
|
||||
3. Create a project if `INITIAL_PROJECT_NAME` is provided
|
||||
4. Set up both development and production environments for the project
|
||||
|
||||
After successful setup, you'll be able to log in with the provided credentials immediately.
|
||||
|
||||
## Example Docker Compose Configuration
|
||||
|
||||
Here's an example of how to configure these variables in a Docker Compose file:
|
||||
|
||||
```yaml
|
||||
version: "3.3"
|
||||
services:
|
||||
formbricks:
|
||||
image: ghcr.io/formbricks/formbricks:latest
|
||||
environment:
|
||||
# Required environment variables
|
||||
WEBAPP_URL: "https://formbricks.example.com"
|
||||
NEXTAUTH_URL: "https://formbricks.example.com"
|
||||
DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/formbricks?schema=public"
|
||||
NEXTAUTH_SECRET: "your-nextauth-secret"
|
||||
ENCRYPTION_KEY: "your-encryption-key"
|
||||
CRON_SECRET: "your-cron-secret"
|
||||
|
||||
# Automated setup variables
|
||||
INITIAL_USER_EMAIL: "admin@example.com"
|
||||
INITIAL_USER_PASSWORD: "your-secure-password"
|
||||
INITIAL_ORGANIZATION_NAME: "Acme Corp"
|
||||
INITIAL_PROJECT_NAME: "Customer Feedback"
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
- Use a strong, unique password for the admin user
|
||||
- Consider removing or changing the environment variables after the initial setup
|
||||
- In production environments, ensure these variables are securely managed and not exposed in logs or configuration files
|
||||
- Use Docker secrets or environment variable management systems in container orchestration platforms
|
||||
|
||||
## Limitations
|
||||
|
||||
- The automated setup only works if no users exist in the database
|
||||
- If the setup process is interrupted, you may need to manually clean up the database before retrying
|
||||
- The feature cannot be used to create additional users after the first user exists
|
||||
@@ -65,10 +65,14 @@ These variables are present inside your machine's docker-compose file. Restart t
|
||||
| PROMETHEUS_ENABLED | Enables Prometheus metrics if set to 1. | optional | |
|
||||
| PROMETHEUS_EXPORTER_PORT | Port for Prometheus metrics. | optional | 9090 |
|
||||
| DOCKER_CRON_ENABLED | Controls whether cron jobs run in the Docker image. Set to 0 to disable (useful for cluster setups). | optional | 1 |
|
||||
| DEFAULT_TEAM_ID | Default team ID for new users. | optional | |
|
||||
| SURVEY_URL | Set this to change the domain of the survey. | optional | WEBAPP_URL |
|
||||
| SENTRY_DSN | Set this to track errors and monitor performance in Sentry. | optional |
|
||||
| SENTRY_AUTH_TOKEN | Set this if you want to make errors more readable in Sentry. | optional |
|
||||
| DISABLE_USER_MANAGEMENT | Set this to hide the user management UI. | optional |
|
||||
| SENTRY_DSN | Set this to track errors and monitor performance in Sentry. | optional | |
|
||||
| SENTRY_AUTH_TOKEN | Set this if you want to make errors more readable in Sentry. | optional | |
|
||||
| DISABLE_USER_MANAGEMENT | Set this to hide the user management UI. | optional | |
|
||||
| INITIAL_USER_EMAIL | Set this to create an initial user with the given email. | optional | |
|
||||
| INITIAL_USER_PASSWORD | Set this to create an initial user with the given password. | optional | |
|
||||
| INITIAL_ORGANIZATION_NAME | Set this to create an initial user with the given organization. | optional | |
|
||||
| INITIAL_PROJECT_NAME | Set this to create an initial user with the given project. | optional | |
|
||||
| DEFAULT_TEAM_ID | Default team ID for new users. | optional | | | optional |
|
||||
|
||||
Note: If you want to configure something that is not possible via above, please open an issue on our GitHub repo here or reach out to us on Github Discussions and we’ll try our best to work out a solution with you.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import type { MigrationScript } from "../../src/scripts/migration-runner";
|
||||
|
||||
export const moveApiKeysToApiKeysNew: MigrationScript = {
|
||||
@@ -65,13 +66,23 @@ export const moveApiKeysToApiKeysNew: MigrationScript = {
|
||||
`;
|
||||
|
||||
// Create the API key environment relation using Prisma
|
||||
await tx.apiKeyEnvironment.create({
|
||||
data: {
|
||||
apiKeyId: apiKey.id,
|
||||
environmentId: apiKey.environmentId,
|
||||
permission: "manage",
|
||||
},
|
||||
});
|
||||
await tx.$executeRaw`
|
||||
INSERT INTO "ApiKeyEnvironment" (
|
||||
"id",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"apiKeyId",
|
||||
"environmentId",
|
||||
"permission"
|
||||
) VALUES (
|
||||
${createId()},
|
||||
NOW(),
|
||||
NOW(),
|
||||
${apiKey.id},
|
||||
${apiKey.environmentId},
|
||||
'manage'
|
||||
)
|
||||
`;
|
||||
migratedCount++;
|
||||
} catch (error) {
|
||||
console.error(`Error migrating API key ${apiKey.id}:`, error);
|
||||
|
||||
@@ -13,8 +13,10 @@
|
||||
"db:migrate:dev": "dotenv -e ../../.env -- sh -c \"pnpm prisma generate && tsx ./src/scripts/apply-migrations.ts\"",
|
||||
"db:create-saml-database:deploy": "env SAML_DATABASE_URL=\"${SAML_DATABASE_URL}\" tsx ./src/scripts/create-saml-database.ts",
|
||||
"db:create-saml-database:dev": "dotenv -e ../../.env -- tsx ./src/scripts/create-saml-database.ts",
|
||||
"db:initial-user-setup:deploy": "env INITIAL_USER_EMAIL=\"${INITIAL_USER_EMAIL}\" INITIAL_USER_PASSWORD=\"${INITIAL_USER_PASSWORD}\" INITIAL_ORGANIZATION_NAME=\"${INITIAL_ORGANIZATION_NAME}\" INITIAL_PROJECT_NAME=\"${INITIAL_PROJECT_NAME}\" tsx ./src/scripts/initial-user-setup.ts",
|
||||
"db:initial-user-setup:dev": "dotenv -e ../../.env -- tsx ./src/scripts/initial-user-setup.ts",
|
||||
"db:push": "prisma db push --accept-data-loss",
|
||||
"db:setup": "pnpm db:migrate:dev && pnpm db:create-saml-database:dev",
|
||||
"db:setup": "pnpm db:migrate:dev && pnpm db:create-saml-database:dev && pnpm db:initial-user-setup:dev",
|
||||
"db:start": "pnpm db:setup",
|
||||
"format": "prisma format",
|
||||
"generate": "prisma generate",
|
||||
@@ -27,8 +29,9 @@
|
||||
"@formbricks/logger": "workspace:*",
|
||||
"@paralleldrive/cuid2": "2.2.2",
|
||||
"@prisma/client": "6.7.0",
|
||||
"zod": "3.24.4",
|
||||
"zod-openapi": "4.2.4"
|
||||
"bcryptjs": "3.0.2",
|
||||
"zod-openapi": "4.2.4",
|
||||
"zod": "3.24.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@formbricks/config-typescript": "workspace:*",
|
||||
|
||||
307
packages/database/src/scripts/initial-user-setup.ts
Normal file
307
packages/database/src/scripts/initial-user-setup.ts
Normal file
@@ -0,0 +1,307 @@
|
||||
import { createId } from "@paralleldrive/cuid2";
|
||||
import { hash } from "bcryptjs";
|
||||
import { z } from "zod";
|
||||
import { logger } from "@formbricks/logger";
|
||||
import { ZOrganization } from "../../zod/organizations";
|
||||
import { prisma } from "../client";
|
||||
|
||||
const { INITIAL_USER_EMAIL, INITIAL_USER_PASSWORD, INITIAL_ORGANIZATION_NAME, INITIAL_PROJECT_NAME } =
|
||||
process.env;
|
||||
|
||||
export const isFreshInstance = async (): Promise<boolean> => {
|
||||
try {
|
||||
const [userResult, organizationResult] = await Promise.all([
|
||||
prisma.$queryRaw<[{ user_count: number }]>`
|
||||
SELECT COUNT(*)::integer AS user_count FROM "User"
|
||||
`,
|
||||
prisma.$queryRaw<[{ org_count: number }]>`
|
||||
SELECT COUNT(*)::integer AS org_count FROM "Organization"
|
||||
`,
|
||||
]);
|
||||
|
||||
const userCount = userResult[0].user_count;
|
||||
const organizationCount = organizationResult[0].org_count;
|
||||
|
||||
return userCount === 0 && organizationCount === 0;
|
||||
} catch (error) {
|
||||
logger.error(error, "Error checking if instance is fresh:");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const isValidEmail = (email: string): boolean => {
|
||||
const ZUserEmail = z.string().max(255).email({ message: "Invalid email" });
|
||||
const parseResult = ZUserEmail.safeParse(email);
|
||||
return parseResult.success;
|
||||
};
|
||||
|
||||
const isValidPassword = (password: string): boolean => {
|
||||
const ZUserPassword = z
|
||||
.string()
|
||||
.min(8)
|
||||
.max(128, { message: "Password must be 128 characters or less" })
|
||||
.regex(/^(?=.*[A-Z])(?=.*\d).*$/);
|
||||
const parseResult = ZUserPassword.safeParse(password);
|
||||
return parseResult.success;
|
||||
};
|
||||
|
||||
const isValidOrganizationName = (name: string): boolean => {
|
||||
const parseResult = ZOrganization.pick({ name: true }).safeParse({ name });
|
||||
return parseResult.success;
|
||||
};
|
||||
|
||||
const isValidProjectName = (name: string): boolean => {
|
||||
const ZProjectName = z.string().trim().min(1, { message: "Project name cannot be empty" });
|
||||
const parseResult = ZProjectName.safeParse(name);
|
||||
return parseResult.success;
|
||||
};
|
||||
|
||||
const validateEnvironmentVariables = (): boolean => {
|
||||
if (INITIAL_USER_EMAIL && !isValidEmail(INITIAL_USER_EMAIL)) {
|
||||
logger.error("Invalid email format. Please provide a valid email.");
|
||||
return false;
|
||||
}
|
||||
if (INITIAL_USER_PASSWORD && !isValidPassword(INITIAL_USER_PASSWORD)) {
|
||||
logger.error("Invalid password format. Please provide a valid password.");
|
||||
return false;
|
||||
}
|
||||
if (INITIAL_ORGANIZATION_NAME && !isValidOrganizationName(INITIAL_ORGANIZATION_NAME)) {
|
||||
logger.error("Invalid organization name format. Please provide a valid organization name.");
|
||||
return false;
|
||||
}
|
||||
if (INITIAL_PROJECT_NAME && !isValidProjectName(INITIAL_PROJECT_NAME)) {
|
||||
logger.error("Invalid project name format. Please provide a valid project name.");
|
||||
return false;
|
||||
}
|
||||
if (INITIAL_PROJECT_NAME && !INITIAL_ORGANIZATION_NAME) {
|
||||
logger.error(
|
||||
"INITIAL_PROJECT_NAME is set but INITIAL_ORGANIZATION_NAME is missing. " +
|
||||
"A project cannot be created without an organization."
|
||||
);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const createEnvironment = async (
|
||||
tx: Parameters<Parameters<typeof prisma.$transaction>[0]>[0],
|
||||
type: "development" | "production",
|
||||
projectId: string
|
||||
): Promise<void> => {
|
||||
const now = new Date();
|
||||
const envId = createId();
|
||||
|
||||
await tx.$executeRawUnsafe(
|
||||
`
|
||||
INSERT INTO "Environment" (
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"type",
|
||||
"projectId",
|
||||
"widgetSetupCompleted",
|
||||
"appSetupCompleted"
|
||||
) VALUES (
|
||||
$1, $2, $3, $4::"EnvironmentType", $5, $6, $7
|
||||
)
|
||||
`,
|
||||
envId,
|
||||
now,
|
||||
now,
|
||||
type,
|
||||
projectId,
|
||||
false,
|
||||
false
|
||||
);
|
||||
};
|
||||
|
||||
const insertUser = async (
|
||||
tx: Parameters<Parameters<typeof prisma.$transaction>[0]>[0],
|
||||
userId: string,
|
||||
email: string,
|
||||
hashedPassword: string,
|
||||
now: Date
|
||||
): Promise<void> => {
|
||||
await tx.$executeRaw`
|
||||
INSERT INTO "User" (
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"name",
|
||||
"email",
|
||||
"password",
|
||||
"email_verified",
|
||||
"locale"
|
||||
) VALUES (
|
||||
${userId},
|
||||
${now},
|
||||
${now},
|
||||
'Admin',
|
||||
${email.toLowerCase()},
|
||||
${hashedPassword},
|
||||
${now},
|
||||
'en-US'
|
||||
)
|
||||
`;
|
||||
};
|
||||
|
||||
const createInitialUser = async (
|
||||
email: string,
|
||||
password: string,
|
||||
organizationName?: string,
|
||||
projectName?: string
|
||||
): Promise<void> => {
|
||||
const hashedPassword = await hash(password, 12);
|
||||
const userId = createId();
|
||||
const now = new Date();
|
||||
|
||||
// Start a transaction for all operations
|
||||
await prisma.$transaction(async (tx) => {
|
||||
if (!organizationName) {
|
||||
// Create only a user without an organization using raw query
|
||||
await insertUser(tx, userId, email, hashedPassword, now);
|
||||
return;
|
||||
}
|
||||
|
||||
// Create user with organization and optional project
|
||||
const organizationId = createId();
|
||||
|
||||
// Create user
|
||||
await insertUser(tx, userId, email, hashedPassword, now);
|
||||
|
||||
// Create organization with billing information
|
||||
const billingData = JSON.stringify({
|
||||
plan: "free",
|
||||
limits: { projects: 3, monthly: { responses: 1500, miu: 2000 } },
|
||||
stripeCustomerId: null,
|
||||
periodStart: now,
|
||||
period: "monthly",
|
||||
});
|
||||
|
||||
await tx.$executeRaw`
|
||||
INSERT INTO "Organization" (
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"name",
|
||||
"billing",
|
||||
"whitelabel",
|
||||
"isAIEnabled"
|
||||
) VALUES (
|
||||
${organizationId},
|
||||
${now},
|
||||
${now},
|
||||
${organizationName},
|
||||
${billingData}::jsonb,
|
||||
'{}'::jsonb,
|
||||
false
|
||||
)
|
||||
`;
|
||||
|
||||
// Create membership linking user to organization
|
||||
await tx.$executeRaw`
|
||||
INSERT INTO "Membership" (
|
||||
"userId",
|
||||
"organizationId",
|
||||
"accepted",
|
||||
"role"
|
||||
) VALUES (
|
||||
${userId},
|
||||
${organizationId},
|
||||
true,
|
||||
'owner'
|
||||
)
|
||||
`;
|
||||
|
||||
if (projectName) {
|
||||
// Create project
|
||||
const projectId = createId();
|
||||
|
||||
await tx.$executeRaw`
|
||||
INSERT INTO "Project" (
|
||||
"id",
|
||||
"created_at",
|
||||
"updated_at",
|
||||
"name",
|
||||
"organizationId",
|
||||
"styling",
|
||||
"config",
|
||||
"recontactDays",
|
||||
"linkSurveyBranding",
|
||||
"inAppSurveyBranding",
|
||||
"placement",
|
||||
"clickOutsideClose",
|
||||
"darkOverlay"
|
||||
) VALUES (
|
||||
${projectId},
|
||||
${now},
|
||||
${now},
|
||||
${projectName},
|
||||
${organizationId},
|
||||
'{"allowStyleOverwrite":true}'::jsonb,
|
||||
'{"channel": "link", "industry":"other"}'::jsonb,
|
||||
7,
|
||||
true,
|
||||
true,
|
||||
'bottomRight',
|
||||
true,
|
||||
false
|
||||
)
|
||||
`;
|
||||
|
||||
await createEnvironment(tx, "development", projectId);
|
||||
await createEnvironment(tx, "production", projectId);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const initialUserSetup = async (): Promise<boolean> => {
|
||||
try {
|
||||
logger.info("Checking if initial environment setup is needed...");
|
||||
if (!validateEnvironmentVariables()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!INITIAL_USER_EMAIL || !INITIAL_USER_PASSWORD) {
|
||||
logger.info("No initial user credentials provided. Skipping automated setup.");
|
||||
return true;
|
||||
}
|
||||
|
||||
const freshInstance = await isFreshInstance();
|
||||
if (!freshInstance) {
|
||||
logger.info("Not a fresh instance (users or organizations exist). Skipping initial setup.");
|
||||
return true;
|
||||
}
|
||||
|
||||
logger.info("Fresh instance detected. Creating initial admin user...");
|
||||
await createInitialUser(
|
||||
INITIAL_USER_EMAIL,
|
||||
INITIAL_USER_PASSWORD,
|
||||
INITIAL_ORGANIZATION_NAME,
|
||||
INITIAL_PROJECT_NAME
|
||||
);
|
||||
|
||||
logger.info(`
|
||||
✅ Successfully created initial admin user${INITIAL_ORGANIZATION_NAME ? " and organization" : ""}:
|
||||
- Email: ${INITIAL_USER_EMAIL}
|
||||
${INITIAL_ORGANIZATION_NAME ? `- Organization: ${INITIAL_ORGANIZATION_NAME}` : ""}
|
||||
${INITIAL_PROJECT_NAME && INITIAL_ORGANIZATION_NAME ? `- Project: ${INITIAL_PROJECT_NAME}` : ""}
|
||||
|
||||
You can now log in with the credentials provided in the environment variables.
|
||||
`);
|
||||
|
||||
return true;
|
||||
} catch (error) {
|
||||
console.error("Error during initial environment setup:", error);
|
||||
logger.error(error, "Error during initial environment setup:");
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
initialUserSetup()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((error: unknown) => {
|
||||
logger.error(error, "Error during initial user setup");
|
||||
});
|
||||
35
pnpm-lock.yaml
generated
35
pnpm-lock.yaml
generated
@@ -564,10 +564,10 @@ importers:
|
||||
version: 8.32.1(eslint@8.57.0)(typescript@5.8.3)
|
||||
'@vercel/style-guide':
|
||||
specifier: 6.0.0
|
||||
version: 6.0.0(@next/eslint-plugin-next@15.3.2)(eslint@8.57.0)(prettier@3.5.3)(typescript@5.8.3)(vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.29.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.7.1))
|
||||
version: 6.0.0(@next/eslint-plugin-next@15.3.2)(eslint@8.57.0)(prettier@3.5.3)(typescript@5.8.3)(vitest@3.1.3(tsx@4.19.4))
|
||||
'@vitest/eslint-plugin':
|
||||
specifier: 1.1.44
|
||||
version: 1.1.44(@typescript-eslint/utils@8.32.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.29.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.7.1))
|
||||
version: 1.1.44(@typescript-eslint/utils@8.32.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.1.3(tsx@4.19.4))
|
||||
eslint-config-next:
|
||||
specifier: 15.3.2
|
||||
version: 15.3.2(eslint@8.57.0)(typescript@5.8.3)
|
||||
@@ -631,6 +631,9 @@ importers:
|
||||
'@prisma/client':
|
||||
specifier: 6.7.0
|
||||
version: 6.7.0(prisma@6.7.0(typescript@5.8.3))(typescript@5.8.3)
|
||||
bcryptjs:
|
||||
specifier: 3.0.2
|
||||
version: 3.0.2
|
||||
zod:
|
||||
specifier: 3.24.4
|
||||
version: 3.24.4
|
||||
@@ -14948,7 +14951,7 @@ snapshots:
|
||||
satori: 0.12.2
|
||||
yoga-wasm-web: 0.3.3
|
||||
|
||||
'@vercel/style-guide@6.0.0(@next/eslint-plugin-next@15.3.2)(eslint@8.57.0)(prettier@3.5.3)(typescript@5.8.3)(vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.29.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.7.1))':
|
||||
'@vercel/style-guide@6.0.0(@next/eslint-plugin-next@15.3.2)(eslint@8.57.0)(prettier@3.5.3)(typescript@5.8.3)(vitest@3.1.3(tsx@4.19.4))':
|
||||
dependencies:
|
||||
'@babel/core': 7.26.0
|
||||
'@babel/eslint-parser': 7.27.0(@babel/core@7.26.0)(eslint@8.57.0)
|
||||
@@ -14956,7 +14959,7 @@ snapshots:
|
||||
'@typescript-eslint/eslint-plugin': 7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)
|
||||
'@typescript-eslint/parser': 7.18.0(eslint@8.57.0)(typescript@5.8.3)
|
||||
eslint-config-prettier: 9.1.0(eslint@8.57.0)
|
||||
eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0)
|
||||
eslint-import-resolver-alias: 1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0))
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0)
|
||||
eslint-plugin-eslint-comments: 3.2.0(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0)
|
||||
@@ -14968,7 +14971,7 @@ snapshots:
|
||||
eslint-plugin-testing-library: 6.5.0(eslint@8.57.0)(typescript@5.8.3)
|
||||
eslint-plugin-tsdoc: 0.2.17
|
||||
eslint-plugin-unicorn: 51.0.1(eslint@8.57.0)
|
||||
eslint-plugin-vitest: 0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.29.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.7.1))
|
||||
eslint-plugin-vitest: 0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.1.3(tsx@4.19.4))
|
||||
prettier-plugin-packagejson: 2.5.10(prettier@3.5.3)
|
||||
optionalDependencies:
|
||||
'@next/eslint-plugin-next': 15.3.2
|
||||
@@ -15011,7 +15014,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/eslint-plugin@1.1.44(@typescript-eslint/utils@8.32.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.29.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.7.1))':
|
||||
'@vitest/eslint-plugin@1.1.44(@typescript-eslint/utils@8.32.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.1.3(tsx@4.19.4))':
|
||||
dependencies:
|
||||
'@typescript-eslint/utils': 8.32.1(eslint@8.57.0)(typescript@5.8.3)
|
||||
eslint: 8.57.0
|
||||
@@ -15033,7 +15036,7 @@ snapshots:
|
||||
chai: 5.2.0
|
||||
tinyrainbow: 2.0.0
|
||||
|
||||
'@vitest/mocker@3.1.3(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.7.1))':
|
||||
'@vitest/mocker@3.1.3(vite@6.3.5(tsx@4.19.4))':
|
||||
dependencies:
|
||||
'@vitest/spy': 3.1.3
|
||||
estree-walker: 3.0.3
|
||||
@@ -16430,7 +16433,7 @@ snapshots:
|
||||
eslint: 8.57.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0)
|
||||
eslint-plugin-jsx-a11y: 6.10.2(eslint@8.57.0)
|
||||
eslint-plugin-react: 7.37.5(eslint@8.57.0)
|
||||
eslint-plugin-react-hooks: 5.2.0(eslint@8.57.0)
|
||||
@@ -16455,9 +16458,9 @@ snapshots:
|
||||
eslint-plugin-turbo: 2.5.3(eslint@8.57.0)(turbo@2.5.3)
|
||||
turbo: 2.5.3
|
||||
|
||||
eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0):
|
||||
eslint-import-resolver-alias@1.1.2(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0)):
|
||||
dependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0)
|
||||
|
||||
eslint-import-resolver-node@0.3.9:
|
||||
dependencies:
|
||||
@@ -16478,11 +16481,11 @@ snapshots:
|
||||
tinyglobby: 0.2.13
|
||||
unrs-resolver: 1.6.2
|
||||
optionalDependencies:
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@8.32.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)
|
||||
eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0):
|
||||
eslint-module-utils@2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0):
|
||||
dependencies:
|
||||
debug: 3.2.7
|
||||
optionalDependencies:
|
||||
@@ -16534,7 +16537,7 @@ snapshots:
|
||||
doctrine: 2.1.0
|
||||
eslint: 8.57.0
|
||||
eslint-import-resolver-node: 0.3.9
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0)
|
||||
eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.31.0)(eslint@8.57.0))(eslint@8.57.0)
|
||||
hasown: 2.0.2
|
||||
is-core-module: 2.16.1
|
||||
is-glob: 4.0.3
|
||||
@@ -16552,7 +16555,7 @@ snapshots:
|
||||
- eslint-import-resolver-webpack
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0):
|
||||
eslint-plugin-import@2.31.0(@typescript-eslint/parser@8.32.1(eslint@8.57.0)(typescript@5.8.3))(eslint-import-resolver-typescript@3.10.1)(eslint@8.57.0):
|
||||
dependencies:
|
||||
'@rtsao/scc': 1.1.0
|
||||
array-includes: 3.1.8
|
||||
@@ -16702,7 +16705,7 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.29.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.7.1)):
|
||||
eslint-plugin-vitest@0.3.26(@typescript-eslint/eslint-plugin@7.18.0(@typescript-eslint/parser@7.18.0(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3))(eslint@8.57.0)(typescript@5.8.3)(vitest@3.1.3(tsx@4.19.4)):
|
||||
dependencies:
|
||||
'@typescript-eslint/utils': 7.18.0(eslint@8.57.0)(typescript@5.8.3)
|
||||
eslint: 8.57.0
|
||||
@@ -20568,7 +20571,7 @@ snapshots:
|
||||
vitest@3.1.3(@types/debug@4.1.12)(@types/node@22.15.18)(jiti@2.4.2)(jsdom@26.1.0)(lightningcss@1.29.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.7.1):
|
||||
dependencies:
|
||||
'@vitest/expect': 3.1.3
|
||||
'@vitest/mocker': 3.1.3(vite@6.3.5(@types/node@22.15.18)(jiti@2.4.2)(lightningcss@1.29.2)(terser@5.39.1)(tsx@4.19.4)(yaml@2.7.1))
|
||||
'@vitest/mocker': 3.1.3(vite@6.3.5(tsx@4.19.4))
|
||||
'@vitest/pretty-format': 3.1.3
|
||||
'@vitest/runner': 3.1.3
|
||||
'@vitest/snapshot': 3.1.3
|
||||
|
||||
@@ -170,7 +170,11 @@
|
||||
"UNKEY_ROOT_KEY",
|
||||
"PROMETHEUS_ENABLED",
|
||||
"PROMETHEUS_EXPORTER_PORT",
|
||||
"DISABLE_USER_MANAGEMENT"
|
||||
"DISABLE_USER_MANAGEMENT",
|
||||
"INITIAL_USER_EMAIL",
|
||||
"INITIAL_USER_PASSWORD",
|
||||
"INITIAL_ORGANIZATION_NAME",
|
||||
"INITIAL_PROJECT_NAME"
|
||||
],
|
||||
"outputs": ["dist/**", ".next/**"]
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user