mirror of
https://github.com/LukeHagar/dokploy.git
synced 2025-12-06 04:19:37 +00:00
Merge branch 'canary' into feature/stop-grace-period-2227
This commit is contained in:
@@ -21,6 +21,7 @@ export const deploymentStatus = pgEnum("deploymentStatus", [
|
||||
"running",
|
||||
"done",
|
||||
"error",
|
||||
"cancelled",
|
||||
]);
|
||||
|
||||
export const deployments = pgTable("deployment", {
|
||||
|
||||
@@ -68,6 +68,7 @@ export * from "./utils/backups/postgres";
|
||||
export * from "./utils/backups/utils";
|
||||
export * from "./utils/backups/web-server";
|
||||
export * from "./utils/builders/compose";
|
||||
export * from "./utils/startup/cancell-deployments";
|
||||
export * from "./utils/builders/docker-file";
|
||||
export * from "./utils/builders/drop";
|
||||
export * from "./utils/builders/heroku";
|
||||
|
||||
@@ -603,6 +603,21 @@ const BUNNY_CDN_IPS = new Set([
|
||||
"89.187.184.176",
|
||||
]);
|
||||
|
||||
// Arvancloud IP ranges
|
||||
// https://www.arvancloud.ir/fa/ips.txt
|
||||
const ARVANCLOUD_IP_RANGES = [
|
||||
"185.143.232.0/22",
|
||||
"188.229.116.16/29",
|
||||
"94.101.182.0/27",
|
||||
"2.144.3.128/28",
|
||||
"89.45.48.64/28",
|
||||
"37.32.16.0/27",
|
||||
"37.32.17.0/27",
|
||||
"37.32.18.0/27",
|
||||
"37.32.19.0/27",
|
||||
"185.215.232.0/22",
|
||||
];
|
||||
|
||||
const CDN_PROVIDERS: CDNProvider[] = [
|
||||
{
|
||||
name: "cloudflare",
|
||||
@@ -627,6 +642,14 @@ const CDN_PROVIDERS: CDNProvider[] = [
|
||||
warningMessage:
|
||||
"Domain is behind Fastly - actual IP is masked by CDN proxy",
|
||||
},
|
||||
{
|
||||
name: "arvancloud",
|
||||
displayName: "Arvancloud",
|
||||
checkIp: (ip: string) =>
|
||||
ARVANCLOUD_IP_RANGES.some((range) => isIPInCIDR(ip, range)),
|
||||
warningMessage:
|
||||
"Domain is behind Arvancloud - actual IP is masked by CDN proxy",
|
||||
},
|
||||
];
|
||||
|
||||
export const detectCDNProvider = (ip: string): CDNProvider | null => {
|
||||
|
||||
@@ -227,7 +227,7 @@ export const deployCompose = async ({
|
||||
|
||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${
|
||||
compose.environment.projectId
|
||||
}/services/compose/${compose.composeId}?tab=deployments`;
|
||||
}/environment/${compose.environmentId}/services/compose/${compose.composeId}?tab=deployments`;
|
||||
const deployment = await createDeploymentCompose({
|
||||
composeId: composeId,
|
||||
title: titleLog,
|
||||
@@ -335,7 +335,7 @@ export const deployRemoteCompose = async ({
|
||||
|
||||
const buildLink = `${await getDokployUrl()}/dashboard/project/${
|
||||
compose.environment.projectId
|
||||
}/services/compose/${compose.composeId}?tab=deployments`;
|
||||
}/environment/${compose.environmentId}/services/compose/${compose.composeId}?tab=deployments`;
|
||||
const deployment = await createDeploymentCompose({
|
||||
composeId: composeId,
|
||||
title: titleLog,
|
||||
|
||||
@@ -33,6 +33,7 @@ export const sendEmailNotification = async (
|
||||
to: toAddresses.join(", "),
|
||||
subject,
|
||||
html: htmlContent,
|
||||
textEncoding: "base64",
|
||||
});
|
||||
} catch (err) {
|
||||
console.log(err);
|
||||
|
||||
@@ -31,29 +31,51 @@ export const getBitbucketCloneUrl = (
|
||||
apiToken?: string | null;
|
||||
bitbucketUsername?: string | null;
|
||||
appPassword?: string | null;
|
||||
bitbucketEmail?: string | null;
|
||||
bitbucketWorkspaceName?: string | null;
|
||||
} | null,
|
||||
repoClone: string,
|
||||
) => {
|
||||
if (!bitbucketProvider) {
|
||||
throw new Error("Bitbucket provider is required");
|
||||
}
|
||||
return bitbucketProvider.apiToken
|
||||
? `https://x-token-auth:${bitbucketProvider.apiToken}@${repoClone}`
|
||||
: `https://${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}@${repoClone}`;
|
||||
|
||||
if (bitbucketProvider.apiToken) {
|
||||
return `https://x-bitbucket-api-token-auth:${bitbucketProvider.apiToken}@${repoClone}`;
|
||||
}
|
||||
|
||||
// For app passwords, use username:app_password format
|
||||
if (!bitbucketProvider.bitbucketUsername || !bitbucketProvider.appPassword) {
|
||||
throw new Error(
|
||||
"Username and app password are required when not using API token",
|
||||
);
|
||||
}
|
||||
return `https://${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}@${repoClone}`;
|
||||
};
|
||||
|
||||
export const getBitbucketHeaders = (bitbucketProvider: Bitbucket) => {
|
||||
if (bitbucketProvider.apiToken) {
|
||||
// For API tokens, use HTTP Basic auth with email and token
|
||||
// According to Bitbucket docs: email:token for API calls
|
||||
const email =
|
||||
bitbucketProvider.bitbucketEmail || bitbucketProvider.bitbucketUsername;
|
||||
// According to Bitbucket official docs, for API calls with API tokens:
|
||||
// "You will need both your Atlassian account email and an API token"
|
||||
// Use: {atlassian_account_email}:{api_token}
|
||||
|
||||
if (!bitbucketProvider.bitbucketEmail) {
|
||||
throw new Error(
|
||||
"Atlassian account email is required when using API token for API calls",
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
Authorization: `Basic ${Buffer.from(`${email}:${bitbucketProvider.apiToken}`).toString("base64")}`,
|
||||
Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketEmail}:${bitbucketProvider.apiToken}`).toString("base64")}`,
|
||||
};
|
||||
}
|
||||
|
||||
// For app passwords, use HTTP Basic auth with username and app password
|
||||
if (!bitbucketProvider.bitbucketUsername || !bitbucketProvider.appPassword) {
|
||||
throw new Error(
|
||||
"Username and app password are required when not using API token",
|
||||
);
|
||||
}
|
||||
return {
|
||||
Authorization: `Basic ${Buffer.from(`${bitbucketProvider.bitbucketUsername}:${bitbucketProvider.appPassword}`).toString("base64")}`,
|
||||
};
|
||||
|
||||
@@ -99,6 +99,19 @@ export const refreshGiteaToken = async (giteaProviderId: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const buildGiteaCloneUrl = (
|
||||
giteaUrl: string,
|
||||
accessToken: string,
|
||||
owner: string,
|
||||
repository: string,
|
||||
) => {
|
||||
const protocol = giteaUrl.startsWith("http://") ? "http" : "https";
|
||||
const baseUrl = giteaUrl.replace(/^https?:\/\//, "");
|
||||
const repoClone = `${owner}/${repository}.git`;
|
||||
const cloneUrl = `${protocol}://oauth2:${accessToken}@${baseUrl}/${repoClone}`;
|
||||
return cloneUrl;
|
||||
};
|
||||
|
||||
export type ApplicationWithGitea = InferResultType<
|
||||
"applications",
|
||||
{ gitea: true }
|
||||
@@ -148,9 +161,13 @@ export const getGiteaCloneCommand = async (
|
||||
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
|
||||
const baseUrl = gitea?.giteaUrl.replace(/^https?:\/\//, "");
|
||||
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
|
||||
const cloneUrl = `https://oauth2:${gitea?.accessToken}@${baseUrl}/${repoClone}`;
|
||||
const cloneUrl = buildGiteaCloneUrl(
|
||||
gitea?.giteaUrl!,
|
||||
gitea?.accessToken!,
|
||||
giteaOwner!,
|
||||
giteaRepository!,
|
||||
);
|
||||
|
||||
const cloneCommand = `
|
||||
rm -rf ${outputPath};
|
||||
@@ -205,8 +222,12 @@ export const cloneGiteaRepository = async (
|
||||
await recreateDirectory(outputPath);
|
||||
|
||||
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
|
||||
const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, "");
|
||||
const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`;
|
||||
const cloneUrl = buildGiteaCloneUrl(
|
||||
giteaProvider.giteaUrl,
|
||||
giteaProvider.accessToken!,
|
||||
giteaOwner!,
|
||||
giteaRepository!,
|
||||
);
|
||||
|
||||
writeStream.write(`\nCloning Repo ${repoClone} to ${outputPath}...\n`);
|
||||
|
||||
@@ -269,9 +290,12 @@ export const cloneRawGiteaRepository = async (entity: Compose) => {
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
await recreateDirectory(outputPath);
|
||||
|
||||
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
|
||||
const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, "");
|
||||
const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`;
|
||||
const cloneUrl = buildGiteaCloneUrl(
|
||||
giteaProvider.giteaUrl,
|
||||
giteaProvider.accessToken!,
|
||||
giteaOwner!,
|
||||
giteaRepository!,
|
||||
);
|
||||
|
||||
try {
|
||||
await spawnAsync("git", [
|
||||
@@ -317,9 +341,13 @@ export const cloneRawGiteaRepositoryRemote = async (compose: Compose) => {
|
||||
const giteaProvider = await findGiteaById(giteaId);
|
||||
const basePath = COMPOSE_PATH;
|
||||
const outputPath = join(basePath, appName, "code");
|
||||
const repoClone = `${giteaOwner}/${giteaRepository}.git`;
|
||||
const baseUrl = giteaProvider.giteaUrl.replace(/^https?:\/\//, "");
|
||||
const cloneUrl = `https://oauth2:${giteaProvider.accessToken}@${baseUrl}/${repoClone}`;
|
||||
const cloneUrl = buildGiteaCloneUrl(
|
||||
giteaProvider.giteaUrl,
|
||||
giteaProvider.accessToken!,
|
||||
giteaOwner!,
|
||||
giteaRepository!,
|
||||
);
|
||||
|
||||
try {
|
||||
const command = `
|
||||
rm -rf ${outputPath};
|
||||
|
||||
21
packages/server/src/utils/startup/cancell-deployments.ts
Normal file
21
packages/server/src/utils/startup/cancell-deployments.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
import { deployments } from "@dokploy/server/db/schema";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { db } from "../../db/index";
|
||||
|
||||
export const initCancelDeployments = async () => {
|
||||
try {
|
||||
console.log("Setting up cancel deployments....");
|
||||
|
||||
const result = await db
|
||||
.update(deployments)
|
||||
.set({
|
||||
status: "cancelled",
|
||||
})
|
||||
.where(eq(deployments.status, "running"))
|
||||
.returning();
|
||||
|
||||
console.log(`Cancelled ${result.length} deployments`);
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
};
|
||||
Reference in New Issue
Block a user