improve(self-hosting): add resend and send email questions

This commit is contained in:
Carl-Gerhard Lindesvärd
2025-02-17 21:18:35 +01:00
committed by Carl-Gerhard Lindesvärd
parent acc675bd36
commit 7182ca6e4c
3 changed files with 80 additions and 27 deletions

View File

@@ -17,6 +17,8 @@ If you upgrading from a previous version, you'll need to edit your `.env` file i
- `ALLOW_REGISTRATION` - If set to `false` new users will not be able to register (only the first user can register). - `ALLOW_REGISTRATION` - If set to `false` new users will not be able to register (only the first user can register).
- `ALLOW_INVITATION` - If set to `false` new users will not be able to be invited. - `ALLOW_INVITATION` - If set to `false` new users will not be able to be invited.
- `RESEND_API_KEY` - If set, we'll use Resend to send e-mails.
- `EMAIL_SENDER` - The e-mail address that will be used to send e-mails.
## 0.0.6 ## 0.0.6

View File

@@ -13,4 +13,5 @@ DATABASE_URL_DIRECT="$DATABASE_URL_DIRECT"
NEXT_PUBLIC_DASHBOARD_URL="$NEXT_PUBLIC_DASHBOARD_URL" NEXT_PUBLIC_DASHBOARD_URL="$NEXT_PUBLIC_DASHBOARD_URL"
NEXT_PUBLIC_API_URL="$NEXT_PUBLIC_API_URL" NEXT_PUBLIC_API_URL="$NEXT_PUBLIC_API_URL"
COOKIE_SECRET="$COOKIE_SECRET" COOKIE_SECRET="$COOKIE_SECRET"
EMAIL_SENDER=no-repy@openpanel.dev EMAIL_SENDER="$EMAIL_SENDER"
RESEND_API_KEY="$RESEND_API_KEY"

View File

@@ -5,6 +5,25 @@ import bcrypt from 'bcrypt';
import inquirer from 'inquirer'; import inquirer from 'inquirer';
import yaml from 'js-yaml'; import yaml from 'js-yaml';
let envs = {
CLICKHOUSE_URL: '',
REDIS_URL: '',
DATABASE_URL: '',
DOMAIN_NAME: '',
COOKIE_SECRET: generatePassword(32),
RESEND_API_KEY: '',
EMAIL_SENDER: '',
};
type EnvVars = typeof envs;
const addEnvs = (env: Partial<EnvVars>) => {
envs = {
...envs,
...env,
};
};
function generatePassword(length: number) { function generatePassword(length: number) {
const charset = const charset =
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789';
@@ -111,13 +130,7 @@ function removeServiceFromDockerCompose(serviceName: string) {
fs.writeFileSync(dockerComposePath, newYaml); fs.writeFileSync(dockerComposePath, newYaml);
} }
function writeEnvFile(envs: { function writeEnvFile(envs: EnvVars) {
CLICKHOUSE_URL: string;
REDIS_URL: string;
DATABASE_URL: string;
DOMAIN_NAME: string;
COOKIE_SECRET: string;
}) {
const envTemplatePath = path.resolve(__dirname, '.env.template'); const envTemplatePath = path.resolve(__dirname, '.env.template');
const envPath = path.resolve(__dirname, '.env'); const envPath = path.resolve(__dirname, '.env');
const envTemplate = fs.readFileSync(envTemplatePath, 'utf-8'); const envTemplate = fs.readFileSync(envTemplatePath, 'utf-8');
@@ -132,7 +145,9 @@ function writeEnvFile(envs: {
.replace( .replace(
'$NEXT_PUBLIC_API_URL', '$NEXT_PUBLIC_API_URL',
`${stripTrailingSlash(envs.DOMAIN_NAME)}/api`, `${stripTrailingSlash(envs.DOMAIN_NAME)}/api`,
); )
.replace('$RESEND_API_KEY', envs.RESEND_API_KEY)
.replace('$EMAIL_SENDER', envs.EMAIL_SENDER);
fs.writeFileSync( fs.writeFileSync(
envPath, envPath,
@@ -175,10 +190,10 @@ async function initiateOnboarding() {
// Domain name // Domain name
const domainNameResponse = await inquirer.prompt([ const domain = await inquirer.prompt([
{ {
type: 'input', type: 'input',
name: 'domainName', name: 'DOMAIN_NAME',
message: "What's the domain name you want to use?", message: "What's the domain name you want to use?",
default: process.env.DEBUG ? 'http://localhost' : undefined, default: process.env.DEBUG ? 'http://localhost' : undefined,
prefix: '🌐', prefix: '🌐',
@@ -192,6 +207,8 @@ async function initiateOnboarding() {
}, },
]); ]);
addEnvs(domain);
// Dependencies // Dependencies
const dependenciesResponse = await inquirer.prompt([ const dependenciesResponse = await inquirer.prompt([
@@ -205,7 +222,6 @@ async function initiateOnboarding() {
}, },
]); ]);
let envs: Record<string, string> = {};
if (!dependenciesResponse.dependencies.includes('Clickhouse')) { if (!dependenciesResponse.dependencies.includes('Clickhouse')) {
const clickhouseResponse = await inquirer.prompt([ const clickhouseResponse = await inquirer.prompt([
{ {
@@ -217,10 +233,7 @@ async function initiateOnboarding() {
}, },
]); ]);
envs = { addEnvs(clickhouseResponse);
...envs,
...clickhouseResponse,
};
} }
if (!dependenciesResponse.dependencies.includes('Redis')) { if (!dependenciesResponse.dependencies.includes('Redis')) {
@@ -232,10 +245,8 @@ async function initiateOnboarding() {
default: process.env.DEBUG ? 'redis://op-kv:6379' : undefined, default: process.env.DEBUG ? 'redis://op-kv:6379' : undefined,
}, },
]); ]);
envs = {
...envs, addEnvs(redisResponse);
...redisResponse,
};
} }
if (!dependenciesResponse.dependencies.includes('Postgres')) { if (!dependenciesResponse.dependencies.includes('Postgres')) {
@@ -250,10 +261,8 @@ async function initiateOnboarding() {
: undefined, : undefined,
}, },
]); ]);
envs = {
...envs, addEnvs(dbResponse);
...dbResponse,
};
} }
// Proxy // Proxy
@@ -293,6 +302,45 @@ async function initiateOnboarding() {
}, },
]); ]);
const resend = await inquirer.prompt<{
RESEND_API_KEY: string;
}>([
{
type: 'input',
name: 'RESEND_API_KEY',
message: 'Enter your Resend API key (optional):',
},
]);
if (resend.RESEND_API_KEY) {
const emailSender = await inquirer.prompt<{
email: string;
}>([
{
type: 'input',
name: 'EMAIL_SENDER',
default: `no-reply@${envs.DOMAIN_NAME.replace(/https?:\/\//, '')}`,
message: 'The email which will be used to send out emails:',
validate: (value) => {
if (!value) {
return 'Field is required';
}
if (!value.includes('@')) {
return 'Please enter a valid email';
}
return true;
},
},
]);
addEnvs({
...resend,
...emailSender,
});
}
const basicAuth = await inquirer.prompt<{ const basicAuth = await inquirer.prompt<{
password: string; password: string;
}>([ }>([
@@ -324,8 +372,10 @@ async function initiateOnboarding() {
DATABASE_URL: DATABASE_URL:
envs.DATABASE_URL || envs.DATABASE_URL ||
'postgresql://postgres:postgres@op-db:5432/postgres?schema=public', 'postgresql://postgres:postgres@op-db:5432/postgres?schema=public',
DOMAIN_NAME: domainNameResponse.domainName, DOMAIN_NAME: envs.DOMAIN_NAME,
COOKIE_SECRET: generatePassword(32), COOKIE_SECRET: envs.COOKIE_SECRET,
RESEND_API_KEY: envs.RESEND_API_KEY || '',
EMAIL_SENDER: envs.EMAIL_SENDER || '',
}); });
console.log('Updating docker-compose.yml file...\n'); console.log('Updating docker-compose.yml file...\n');
@@ -350,7 +400,7 @@ async function initiateOnboarding() {
if (proxyResponse.proxy === 'Bring my own') { if (proxyResponse.proxy === 'Bring my own') {
removeServiceFromDockerCompose('op-proxy'); removeServiceFromDockerCompose('op-proxy');
} else { } else {
writeCaddyfile(domainNameResponse.domainName, basicAuth.password); writeCaddyfile(envs.DOMAIN_NAME, basicAuth.password);
} }
searchAndReplaceDockerCompose([['$OP_WORKER_REPLICAS', cpus.CPUS]]); searchAndReplaceDockerCompose([['$OP_WORKER_REPLICAS', cpus.CPUS]]);