Merge branch 'canary' into feat/ntfy

This commit is contained in:
Mauricio Siu
2025-09-06 14:12:06 -06:00
132 changed files with 25182 additions and 2589 deletions

View File

@@ -4,6 +4,7 @@ import {
createDeploymentBackup,
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
@@ -13,8 +14,9 @@ export const runComposeBackup = async (
compose: Compose,
backup: BackupSchedule,
) => {
const { projectId, name } = compose;
const project = await findProjectById(projectId);
const { environmentId, name } = compose;
const environment = await findEnvironmentById(environmentId);
const project = await findProjectById(environment.projectId);
const { prefix, databaseType } = backup;
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;

View File

@@ -4,6 +4,7 @@ import {
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import type { Mariadb } from "@dokploy/server/services/mariadb";
import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
@@ -13,8 +14,9 @@ export const runMariadbBackup = async (
mariadb: Mariadb,
backup: BackupSchedule,
) => {
const { projectId, name } = mariadb;
const project = await findProjectById(projectId);
const { environmentId, name } = mariadb;
const environment = await findEnvironmentById(environmentId);
const project = await findProjectById(environment.projectId);
const { prefix } = backup;
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;

View File

@@ -4,14 +4,16 @@ import {
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import type { Mongo } from "@dokploy/server/services/mongo";
import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils";
export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
const { projectId, name } = mongo;
const project = await findProjectById(projectId);
const { environmentId, name } = mongo;
const environment = await findEnvironmentById(environmentId);
const project = await findProjectById(environment.projectId);
const { prefix } = backup;
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;

View File

@@ -4,14 +4,16 @@ import {
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import type { MySql } from "@dokploy/server/services/mysql";
import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
import { getBackupCommand, getS3Credentials, normalizeS3Path } from "./utils";
export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
const { projectId, name } = mysql;
const project = await findProjectById(projectId);
const { environmentId, name } = mysql;
const environment = await findEnvironmentById(environmentId);
const project = await findProjectById(environment.projectId);
const { prefix } = backup;
const destination = backup.destination;
const backupFileName = `${new Date().toISOString()}.sql.gz`;

View File

@@ -4,6 +4,7 @@ import {
updateDeploymentStatus,
} from "@dokploy/server/services/deployment";
import type { Postgres } from "@dokploy/server/services/postgres";
import { findEnvironmentById } from "@dokploy/server/services/environment";
import { findProjectById } from "@dokploy/server/services/project";
import { sendDatabaseBackupNotifications } from "../notifications/database-backup";
import { execAsync, execAsyncRemote } from "../process/execAsync";
@@ -13,8 +14,9 @@ export const runPostgresBackup = async (
postgres: Postgres,
backup: BackupSchedule,
) => {
const { name, projectId } = postgres;
const project = await findProjectById(projectId);
const { name, environmentId } = postgres;
const environment = await findEnvironmentById(environmentId);
const project = await findProjectById(environment.projectId);
const deployment = await createDeploymentBackup({
backupId: backup.backupId,

View File

@@ -80,7 +80,7 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
writeStream.write("Zipped database and filesystem\n");
const uploadCommand = `rclone copyto ${rcloneFlags.join(" ")} "${tempDir}/${backupFileName}" "${s3Path}"`;
writeStream.write(`Running command: ${uploadCommand}\n`);
writeStream.write("Running command to upload backup to S3\n");
await execAsync(uploadCommand);
writeStream.write("Uploaded backup to S3 ✅\n");
writeStream.end();

View File

@@ -22,7 +22,7 @@ import { spawnAsync } from "../process/spawnAsync";
export type ComposeNested = InferResultType<
"compose",
{ project: true; mounts: true; domains: true }
{ environment: { with: { project: true } }; mounts: true; domains: true }
>;
export const buildCompose = async (compose: ComposeNested, logPath: string) => {
const writeStream = createWriteStream(logPath, { flags: "a" });
@@ -72,7 +72,10 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
NODE_ENV: process.env.NODE_ENV,
PATH: process.env.PATH,
...(composeType === "stack" && {
...getEnviromentVariablesObject(compose.env, compose.project.env),
...getEnviromentVariablesObject(
compose.env,
compose.environment.project.env,
),
}),
},
},
@@ -202,7 +205,8 @@ const createEnvFile = (compose: ComposeNested) => {
const envFileContent = prepareEnvironmentVariables(
envContent,
compose.project.env,
compose.environment.project.env,
compose.environment.env,
).join("\n");
if (!existsSync(dirname(envFilePath))) {
@@ -232,7 +236,8 @@ export const getCreateEnvFileCommand = (compose: ComposeNested) => {
const envFileContent = prepareEnvironmentVariables(
envContent,
compose.project.env,
compose.environment.project.env,
compose.environment.env,
).join("\n");
const encodedContent = encodeBase64(envFileContent);
@@ -247,7 +252,7 @@ const getExportEnvCommand = (compose: ComposeNested) => {
const envVars = getEnviromentVariablesObject(
compose.env,
compose.project.env,
compose.environment.project.env,
);
const exports = Object.entries(envVars)
.map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)

View File

@@ -28,7 +28,8 @@ export const buildCustomDocker = async (
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
const args = prepareEnvironmentVariables(
buildArgs,
application.project.env,
application.environment.project.env,
application.environment.env,
);
const dockerContextPath = getDockerContextPath(application);
@@ -51,7 +52,12 @@ export const buildCustomDocker = async (
as it could be publicly exposed.
*/
if (!publishDirectory) {
createEnvFile(dockerFilePath, env, application.project.env);
createEnvFile(
dockerFilePath,
env,
application.environment.project.env,
application.environment.env,
);
}
await spawnAsync(
@@ -92,7 +98,8 @@ export const getDockerCommand = (
dockerFilePath.substring(0, dockerFilePath.lastIndexOf("/") + 1) || ".";
const args = prepareEnvironmentVariables(
buildArgs,
application.project.env,
application.environment.project.env,
application.environment.env,
);
const dockerContextPath =
@@ -121,7 +128,8 @@ export const getDockerCommand = (
command += createEnvFileCommand(
dockerFilePath,
env,
application.project.env,
application.environment.project.env,
application.environment.env,
);
}

View File

@@ -13,7 +13,8 @@ export const buildHeroku = async (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
application.environment.project.env,
application.environment.env,
);
try {
const args = [
@@ -53,7 +54,8 @@ export const getHerokuCommand = (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
application.environment.project.env,
application.environment.env,
);
const args = [

View File

@@ -30,7 +30,7 @@ export type ApplicationNested = InferResultType<
redirects: true;
ports: true;
registry: true;
project: true;
environment: { with: { project: true } };
}
>;
@@ -148,7 +148,8 @@ export const mechanizeDockerContainer = async (
const filesMount = generateFileMounts(appName, application);
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
application.environment.project.env,
application.environment.env,
);
const image = getImageName(application);

View File

@@ -20,7 +20,8 @@ export const buildNixpacks = async (
const buildContainerId = `${appName}-${nanoid(10)}`;
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
application.environment.project.env,
application.environment.env,
);
const writeToStream = (data: string) => {
@@ -101,7 +102,8 @@ export const getNixpacksCommand = (
const buildContainerId = `${appName}-${nanoid(10)}`;
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
application.environment.project.env,
application.environment.env,
);
const args = ["build", buildAppDirectory, "--name", appName];

View File

@@ -12,7 +12,8 @@ export const buildPaketo = async (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
application.environment.project.env,
application.environment.env,
);
try {
const args = [
@@ -52,7 +53,8 @@ export const getPaketoCommand = (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
application.environment.project.env,
application.environment.env,
);
const args = [

View File

@@ -26,7 +26,8 @@ export const buildRailpack = async (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
application.environment.project.env,
application.environment.env,
);
try {
@@ -123,7 +124,8 @@ export const getRailpackCommand = (
const buildAppDirectory = getBuildAppDirectory(application);
const envVariables = prepareEnvironmentVariables(
env,
application.project.env,
application.environment.project.env,
application.environment.env,
);
// Prepare command

View File

@@ -6,14 +6,17 @@ export const createEnvFile = (
directory: string,
env: string | null,
projectEnv?: string | null,
environmentEnv?: string | null,
) => {
const envFilePath = join(dirname(directory), ".env");
if (!existsSync(dirname(envFilePath))) {
mkdirSync(dirname(envFilePath), { recursive: true });
}
const envFileContent = prepareEnvironmentVariables(env, projectEnv).join(
"\n",
);
const envFileContent = prepareEnvironmentVariables(
env,
projectEnv,
environmentEnv,
).join("\n");
writeFileSync(envFilePath, envFileContent);
};
@@ -21,10 +24,13 @@ export const createEnvFileCommand = (
directory: string,
env: string | null,
projectEnv?: string | null,
environmentEnv?: string | null,
) => {
const envFileContent = prepareEnvironmentVariables(env, projectEnv).join(
"\n",
);
const envFileContent = prepareEnvironmentVariables(
env,
projectEnv,
environmentEnv,
).join("\n");
const encodedContent = encodeBase64(envFileContent || "");
const envFilePath = join(dirname(directory), ".env");

View File

@@ -12,7 +12,7 @@ import { getRemoteDocker } from "../servers/remote-docker";
export type MariadbNested = InferResultType<
"mariadb",
{ mounts: true; project: true }
{ mounts: true; environment: { with: { project: true } } }
>;
export const buildMariadb = async (mariadb: MariadbNested) => {
const {
@@ -54,7 +54,8 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
});
const envVariables = prepareEnvironmentVariables(
defaultMariadbEnv,
mariadb.project.env,
mariadb.environment.project.env,
mariadb.environment.env,
);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);

View File

@@ -12,7 +12,7 @@ import { getRemoteDocker } from "../servers/remote-docker";
export type MongoNested = InferResultType<
"mongo",
{ mounts: true; project: true }
{ mounts: true; environment: { with: { project: true } } }
>;
export const buildMongo = async (mongo: MongoNested) => {
@@ -102,7 +102,8 @@ ${command ?? "wait $MONGOD_PID"}`;
const envVariables = prepareEnvironmentVariables(
defaultMongoEnv,
mongo.project.env,
mongo.environment.project.env,
mongo.environment.env,
);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);

View File

@@ -12,7 +12,7 @@ import { getRemoteDocker } from "../servers/remote-docker";
export type MysqlNested = InferResultType<
"mysql",
{ mounts: true; project: true }
{ mounts: true; environment: { with: { project: true } } }
>;
export const buildMysql = async (mysql: MysqlNested) => {
@@ -60,7 +60,8 @@ export const buildMysql = async (mysql: MysqlNested) => {
});
const envVariables = prepareEnvironmentVariables(
defaultMysqlEnv,
mysql.project.env,
mysql.environment.project.env,
mysql.environment.env,
);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);

View File

@@ -12,7 +12,7 @@ import { getRemoteDocker } from "../servers/remote-docker";
export type PostgresNested = InferResultType<
"postgres",
{ mounts: true; project: true }
{ mounts: true; environment: { with: { project: true } } }
>;
export const buildPostgres = async (postgres: PostgresNested) => {
const {
@@ -53,7 +53,8 @@ export const buildPostgres = async (postgres: PostgresNested) => {
});
const envVariables = prepareEnvironmentVariables(
defaultPostgresEnv,
postgres.project.env,
postgres.environment.project.env,
postgres.environment.env,
);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);

View File

@@ -12,7 +12,7 @@ import { getRemoteDocker } from "../servers/remote-docker";
export type RedisNested = InferResultType<
"redis",
{ mounts: true; project: true }
{ mounts: true; environment: { with: { project: true } } }
>;
export const buildRedis = async (redis: RedisNested) => {
const {
@@ -51,7 +51,8 @@ export const buildRedis = async (redis: RedisNested) => {
});
const envVariables = prepareEnvironmentVariables(
defaultRedisEnv,
redis.project.env,
redis.environment.project.env,
redis.environment.env,
);
const volumesMount = generateVolumeMounts(mounts);
const bindsMount = generateBindMounts(mounts);

View File

@@ -259,21 +259,44 @@ export const removeService = async (
export const prepareEnvironmentVariables = (
serviceEnv: string | null,
projectEnv?: string | null,
environmentEnv?: string | null,
) => {
const projectVars = parse(projectEnv ?? "");
const environmentVars = parse(environmentEnv ?? "");
const serviceVars = parse(serviceEnv ?? "");
const resolvedVars = Object.entries(serviceVars).map(([key, value]) => {
let resolvedValue = value;
// Replace project variables
if (projectVars) {
resolvedValue = value.replace(/\$\{\{project\.(.*?)\}\}/g, (_, ref) => {
if (projectVars[ref] !== undefined) {
return projectVars[ref];
}
throw new Error(`Invalid project environment variable: project.${ref}`);
});
resolvedValue = resolvedValue.replace(
/\$\{\{project\.(.*?)\}\}/g,
(_, ref) => {
if (projectVars[ref] !== undefined) {
return projectVars[ref];
}
throw new Error(
`Invalid project environment variable: project.${ref}`,
);
},
);
}
// Replace environment variables
if (environmentVars) {
resolvedValue = resolvedValue.replace(
/\$\{\{environment\.(.*?)\}\}/g,
(_, ref) => {
if (environmentVars[ref] !== undefined) {
return environmentVars[ref];
}
throw new Error(`Invalid environment variable: environment.${ref}`);
},
);
}
// Replace self-references (service variables)
resolvedValue = resolvedValue.replace(/\$\{\{(.*?)\}\}/g, (_, ref) => {
if (serviceVars[ref] !== undefined) {
return serviceVars[ref];
@@ -301,8 +324,9 @@ export const parseEnvironmentKeyValuePair = (
export const getEnviromentVariablesObject = (
input: string | null,
projectEnv?: string | null,
environmentEnv?: string | null,
) => {
const envs = prepareEnvironmentVariables(input, projectEnv);
const envs = prepareEnvironmentVariables(input, projectEnv, environmentEnv);
const jsonObject: Record<string, string> = {};

View File

@@ -42,7 +42,9 @@ export const buildDocker = async (
await mechanizeDockerContainer(application);
writeStream.write("\nDocker Deployed: ✅\n");
} catch (error) {
writeStream.write("❌ Error");
writeStream.write(
`❌ Error: ${error instanceof Error ? error.message : String(error)}`,
);
throw error;
} finally {
writeStream.end();