mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Merge branch 'canary' into ayham291/canary
This commit is contained in:
@@ -1,3 +1,4 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:20.9-slim AS base
|
FROM node:20.9-slim AS base
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:20.9-slim AS base
|
FROM node:20.9-slim AS base
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
# Build stage
|
# Build stage
|
||||||
FROM golang:1.21-alpine3.19 AS builder
|
FROM golang:1.21-alpine3.19 AS builder
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:20.9-slim AS base
|
FROM node:20.9-slim AS base
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
# syntax=docker/dockerfile:1
|
||||||
FROM node:20.9-slim AS base
|
FROM node:20.9-slim AS base
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
|||||||
28
SECURITY.md
Normal file
28
SECURITY.md
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# Dokploy Security Policy
|
||||||
|
|
||||||
|
At Dokploy, security is a top priority. We appreciate the help of security researchers and the community in identifying and reporting vulnerabilities.
|
||||||
|
|
||||||
|
## How to Report a Vulnerability
|
||||||
|
|
||||||
|
If you have discovered a security vulnerability in Dokploy, we ask that you report it responsibly by following these guidelines:
|
||||||
|
|
||||||
|
1. **Contact us:** Send an email to [contact@dokploy.com](mailto:contact@dokploy.com).
|
||||||
|
2. **Provide clear details:** Include as much information as possible to help us understand and reproduce the vulnerability. This should include:
|
||||||
|
* A clear description of the vulnerability.
|
||||||
|
* Steps to reproduce the vulnerability.
|
||||||
|
* Any sample code, screenshots, or videos that might be helpful.
|
||||||
|
* The potential impact of the vulnerability.
|
||||||
|
3. **Do not make the vulnerability public:** Please refrain from publicly disclosing the vulnerability until we have had the opportunity to investigate and address it. This is crucial for protecting our users.
|
||||||
|
4. **Allow us time:** We will endeavor to acknowledge receipt of your report as soon as possible and keep you informed of our progress. The time to resolve the vulnerability may vary depending on its complexity and severity.
|
||||||
|
|
||||||
|
## What We Expect From You
|
||||||
|
|
||||||
|
* Do not access user data or systems beyond what is necessary to demonstrate the vulnerability.
|
||||||
|
* Do not perform denial-of-service (DoS) attacks, spamming, or social engineering.
|
||||||
|
* Do not modify or destroy data that does not belong to you.
|
||||||
|
|
||||||
|
## Our Commitment
|
||||||
|
|
||||||
|
We are committed to working with you quickly and responsibly to address any legitimate security vulnerability.
|
||||||
|
|
||||||
|
Thank you for helping us keep Dokploy secure for everyone.
|
||||||
@@ -247,7 +247,7 @@ export const UpdateVolume = ({
|
|||||||
control={form.control}
|
control={form.control}
|
||||||
name="content"
|
name="content"
|
||||||
render={({ field }) => (
|
render={({ field }) => (
|
||||||
<FormItem>
|
<FormItem className="max-w-full max-w-[45rem]">
|
||||||
<FormLabel>Content</FormLabel>
|
<FormLabel>Content</FormLabel>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
<FormControl>
|
<FormControl>
|
||||||
@@ -256,7 +256,7 @@ export const UpdateVolume = ({
|
|||||||
placeholder={`NODE_ENV=production
|
placeholder={`NODE_ENV=production
|
||||||
PORT=3000
|
PORT=3000
|
||||||
`}
|
`}
|
||||||
className="h-96 font-mono"
|
className="h-96 font-mono w-full"
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ export type ValidationState = {
|
|||||||
error?: string;
|
error?: string;
|
||||||
resolvedIp?: string;
|
resolvedIp?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
|
cdnProvider?: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type ValidationStates = Record<string, ValidationState>;
|
export type ValidationStates = Record<string, ValidationState>;
|
||||||
@@ -119,6 +120,7 @@ export const ShowDomains = ({ id, type }: Props) => {
|
|||||||
isValid: result.isValid,
|
isValid: result.isValid,
|
||||||
error: result.error,
|
error: result.error,
|
||||||
resolvedIp: result.resolvedIp,
|
resolvedIp: result.resolvedIp,
|
||||||
|
cdnProvider: result.cdnProvider,
|
||||||
message: result.error && result.isValid ? result.error : undefined,
|
message: result.error && result.isValid ? result.error : undefined,
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
@@ -354,8 +356,9 @@ export const ShowDomains = ({ id, type }: Props) => {
|
|||||||
) : validationState?.isValid ? (
|
) : validationState?.isValid ? (
|
||||||
<>
|
<>
|
||||||
<CheckCircle2 className="size-3 mr-1" />
|
<CheckCircle2 className="size-3 mr-1" />
|
||||||
{validationState.message
|
{validationState.message &&
|
||||||
? "Behind Cloudflare"
|
validationState.cdnProvider
|
||||||
|
? `Behind ${validationState.cdnProvider}`
|
||||||
: "DNS Valid"}
|
: "DNS Valid"}
|
||||||
</>
|
</>
|
||||||
) : validationState?.error ? (
|
) : validationState?.error ? (
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ export const ShowEnvironment = ({ applicationId }: Props) => {
|
|||||||
currentBuildArgs !== (data?.buildArgs || "");
|
currentBuildArgs !== (data?.buildArgs || "");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data && !hasChanges) {
|
||||||
form.reset({
|
form.reset({
|
||||||
env: data.env || "",
|
env: data.env || "",
|
||||||
buildArgs: data.buildArgs || "",
|
buildArgs: data.buildArgs || "",
|
||||||
|
|||||||
@@ -44,8 +44,10 @@ export const ComposeFileEditor = ({ composeId }: Props) => {
|
|||||||
resolver: zodResolver(AddComposeFile),
|
resolver: zodResolver(AddComposeFile),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const composeFile = form.watch("composeFile");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (data) {
|
if (data && !composeFile) {
|
||||||
form.reset({
|
form.reset({
|
||||||
composeFile: data.composeFile || "",
|
composeFile: data.composeFile || "",
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ const AddProjectSchema = z.object({
|
|||||||
(name) => {
|
(name) => {
|
||||||
const trimmedName = name.trim();
|
const trimmedName = name.trim();
|
||||||
const validNameRegex =
|
const validNameRegex =
|
||||||
/^[\p{L}\p{N}_-][\p{L}\p{N}\s_-]*[\p{L}\p{N}_-]$/u;
|
/^[\p{L}\p{N}_-][\p{L}\p{N}\s_.-]*[\p{L}\p{N}_-]$/u;
|
||||||
return validNameRegex.test(trimmedName);
|
return validNameRegex.test(trimmedName);
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,80 +1,89 @@
|
|||||||
export const extractExpirationDate = (certData: string): Date | null => {
|
export const extractExpirationDate = (certData: string): Date | null => {
|
||||||
try {
|
try {
|
||||||
const match = certData.match(
|
// Decode PEM base64 to DER binary
|
||||||
/-----BEGIN CERTIFICATE-----\s*([^-]+)\s*-----END CERTIFICATE-----/,
|
const b64 = certData.replace(/-----[^-]+-----/g, "").replace(/\s+/g, "");
|
||||||
);
|
const binStr = atob(b64);
|
||||||
if (!match?.[1]) return null;
|
const der = new Uint8Array(binStr.length);
|
||||||
|
for (let i = 0; i < binStr.length; i++) {
|
||||||
const base64Cert = match[1].replace(/\s/g, "");
|
der[i] = binStr.charCodeAt(i);
|
||||||
const binaryStr = window.atob(base64Cert);
|
|
||||||
const bytes = new Uint8Array(binaryStr.length);
|
|
||||||
|
|
||||||
for (let i = 0; i < binaryStr.length; i++) {
|
|
||||||
bytes[i] = binaryStr.charCodeAt(i);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ASN.1 tag for UTCTime is 0x17, GeneralizedTime is 0x18
|
let offset = 0;
|
||||||
// We need to find the second occurrence of either tag as it's the "not after" (expiration) date
|
|
||||||
let dateFound = false;
|
|
||||||
for (let i = 0; i < bytes.length - 2; i++) {
|
|
||||||
// Look for sequence containing validity period (0x30)
|
|
||||||
if (bytes[i] === 0x30) {
|
|
||||||
// Check next bytes for UTCTime or GeneralizedTime
|
|
||||||
let j = i + 1;
|
|
||||||
while (j < bytes.length - 2) {
|
|
||||||
if (bytes[j] === 0x17 || bytes[j] === 0x18) {
|
|
||||||
const dateType = bytes[j];
|
|
||||||
const dateLength = bytes[j + 1];
|
|
||||||
if (typeof dateLength === "undefined") break;
|
|
||||||
|
|
||||||
if (!dateFound) {
|
// Helper: read ASN.1 length field
|
||||||
// Skip "not before" date
|
function readLength(pos: number): { length: number; offset: number } {
|
||||||
dateFound = true;
|
let len = der[pos++];
|
||||||
j += dateLength + 2;
|
if (len & 0x80) {
|
||||||
continue;
|
const bytes = len & 0x7f;
|
||||||
}
|
len = 0;
|
||||||
|
for (let i = 0; i < bytes; i++) {
|
||||||
// Found "not after" date
|
len = (len << 8) + der[pos++];
|
||||||
let dateStr = "";
|
|
||||||
for (let k = 0; k < dateLength; k++) {
|
|
||||||
const charCode = bytes[j + 2 + k];
|
|
||||||
if (typeof charCode === "undefined") continue;
|
|
||||||
dateStr += String.fromCharCode(charCode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dateType === 0x17) {
|
|
||||||
// UTCTime (YYMMDDhhmmssZ)
|
|
||||||
const year = Number.parseInt(dateStr.slice(0, 2));
|
|
||||||
const fullYear = year >= 50 ? 1900 + year : 2000 + year;
|
|
||||||
return new Date(
|
|
||||||
Date.UTC(
|
|
||||||
fullYear,
|
|
||||||
Number.parseInt(dateStr.slice(2, 4)) - 1,
|
|
||||||
Number.parseInt(dateStr.slice(4, 6)),
|
|
||||||
Number.parseInt(dateStr.slice(6, 8)),
|
|
||||||
Number.parseInt(dateStr.slice(8, 10)),
|
|
||||||
Number.parseInt(dateStr.slice(10, 12)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// GeneralizedTime (YYYYMMDDhhmmssZ)
|
|
||||||
return new Date(
|
|
||||||
Date.UTC(
|
|
||||||
Number.parseInt(dateStr.slice(0, 4)),
|
|
||||||
Number.parseInt(dateStr.slice(4, 6)) - 1,
|
|
||||||
Number.parseInt(dateStr.slice(6, 8)),
|
|
||||||
Number.parseInt(dateStr.slice(8, 10)),
|
|
||||||
Number.parseInt(dateStr.slice(10, 12)),
|
|
||||||
Number.parseInt(dateStr.slice(12, 14)),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
j++;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return { length: len, offset: pos };
|
||||||
}
|
}
|
||||||
return null;
|
|
||||||
|
// Skip the outer certificate sequence
|
||||||
|
if (der[offset++] !== 0x30) throw new Error("Expected sequence");
|
||||||
|
({ offset } = readLength(offset));
|
||||||
|
|
||||||
|
// Skip tbsCertificate sequence
|
||||||
|
if (der[offset++] !== 0x30) throw new Error("Expected tbsCertificate");
|
||||||
|
({ offset } = readLength(offset));
|
||||||
|
|
||||||
|
// Check for optional version field (context-specific tag [0])
|
||||||
|
if (der[offset] === 0xa0) {
|
||||||
|
offset++;
|
||||||
|
const versionLen = readLength(offset);
|
||||||
|
offset = versionLen.offset + versionLen.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skip serialNumber, signature, issuer
|
||||||
|
for (let i = 0; i < 3; i++) {
|
||||||
|
if (der[offset] !== 0x30 && der[offset] !== 0x02)
|
||||||
|
throw new Error("Unexpected structure");
|
||||||
|
offset++;
|
||||||
|
const fieldLen = readLength(offset);
|
||||||
|
offset = fieldLen.offset + fieldLen.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validity sequence (notBefore and notAfter)
|
||||||
|
if (der[offset++] !== 0x30) throw new Error("Expected validity sequence");
|
||||||
|
const validityLen = readLength(offset);
|
||||||
|
offset = validityLen.offset;
|
||||||
|
|
||||||
|
// notBefore
|
||||||
|
offset++;
|
||||||
|
const notBeforeLen = readLength(offset);
|
||||||
|
offset = notBeforeLen.offset + notBeforeLen.length;
|
||||||
|
|
||||||
|
// notAfter
|
||||||
|
offset++;
|
||||||
|
const notAfterLen = readLength(offset);
|
||||||
|
const notAfterStr = new TextDecoder().decode(
|
||||||
|
der.slice(notAfterLen.offset, notAfterLen.offset + notAfterLen.length),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Parse GeneralizedTime (15 chars) or UTCTime (13 chars)
|
||||||
|
function parseTime(str: string): Date {
|
||||||
|
if (str.length === 13) {
|
||||||
|
// UTCTime YYMMDDhhmmssZ
|
||||||
|
const year = parseInt(str.slice(0, 2), 10);
|
||||||
|
const fullYear = year < 50 ? 2000 + year : 1900 + year;
|
||||||
|
return new Date(
|
||||||
|
`${fullYear}-${str.slice(2, 4)}-${str.slice(4, 6)}T${str.slice(6, 8)}:${str.slice(8, 10)}:${str.slice(10, 12)}Z`,
|
||||||
|
);
|
||||||
|
} else if (str.length === 15) {
|
||||||
|
// GeneralizedTime YYYYMMDDhhmmssZ
|
||||||
|
return new Date(
|
||||||
|
`${str.slice(0, 4)}-${str.slice(4, 6)}-${str.slice(6, 8)}T${str.slice(8, 10)}:${str.slice(10, 12)}:${str.slice(12, 14)}Z`,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw new Error("Invalid ASN.1 time format");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return parseTime(notAfterStr);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error parsing certificate:", error);
|
console.error("Error parsing certificate:", error);
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -41,6 +41,7 @@ const addInvitation = z.object({
|
|||||||
.min(1, "Email is required")
|
.min(1, "Email is required")
|
||||||
.email({ message: "Invalid email" }),
|
.email({ message: "Invalid email" }),
|
||||||
role: z.enum(["member", "admin"]),
|
role: z.enum(["member", "admin"]),
|
||||||
|
notificationId: z.string().optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
type AddInvitation = z.infer<typeof addInvitation>;
|
type AddInvitation = z.infer<typeof addInvitation>;
|
||||||
@@ -49,6 +50,10 @@ export const AddInvitation = () => {
|
|||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
const utils = api.useUtils();
|
const utils = api.useUtils();
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
const { data: emailProviders } =
|
||||||
|
api.notification.getEmailProviders.useQuery();
|
||||||
|
const { mutateAsync: sendInvitation } = api.user.sendInvitation.useMutation();
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const { data: activeOrganization } = authClient.useActiveOrganization();
|
const { data: activeOrganization } = authClient.useActiveOrganization();
|
||||||
|
|
||||||
@@ -56,6 +61,7 @@ export const AddInvitation = () => {
|
|||||||
defaultValues: {
|
defaultValues: {
|
||||||
email: "",
|
email: "",
|
||||||
role: "member",
|
role: "member",
|
||||||
|
notificationId: "",
|
||||||
},
|
},
|
||||||
resolver: zodResolver(addInvitation),
|
resolver: zodResolver(addInvitation),
|
||||||
});
|
});
|
||||||
@@ -74,7 +80,20 @@ export const AddInvitation = () => {
|
|||||||
if (result.error) {
|
if (result.error) {
|
||||||
setError(result.error.message || "");
|
setError(result.error.message || "");
|
||||||
} else {
|
} else {
|
||||||
toast.success("Invitation created");
|
if (!isCloud && data.notificationId) {
|
||||||
|
await sendInvitation({
|
||||||
|
invitationId: result.data.id,
|
||||||
|
notificationId: data.notificationId || "",
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
toast.success("Invitation created and email sent");
|
||||||
|
})
|
||||||
|
.catch((error: any) => {
|
||||||
|
toast.error(error.message);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast.success("Invitation created");
|
||||||
|
}
|
||||||
setError(null);
|
setError(null);
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
}
|
}
|
||||||
@@ -149,6 +168,47 @@ export const AddInvitation = () => {
|
|||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{!isCloud && (
|
||||||
|
<FormField
|
||||||
|
control={form.control}
|
||||||
|
name="notificationId"
|
||||||
|
render={({ field }) => {
|
||||||
|
return (
|
||||||
|
<FormItem>
|
||||||
|
<FormLabel>Email Provider</FormLabel>
|
||||||
|
<Select
|
||||||
|
onValueChange={field.onChange}
|
||||||
|
defaultValue={field.value}
|
||||||
|
>
|
||||||
|
<FormControl>
|
||||||
|
<SelectTrigger>
|
||||||
|
<SelectValue placeholder="Select an email provider" />
|
||||||
|
</SelectTrigger>
|
||||||
|
</FormControl>
|
||||||
|
<SelectContent>
|
||||||
|
{emailProviders?.map((provider) => (
|
||||||
|
<SelectItem
|
||||||
|
key={provider.notificationId}
|
||||||
|
value={provider.notificationId}
|
||||||
|
>
|
||||||
|
{provider.name}
|
||||||
|
</SelectItem>
|
||||||
|
))}
|
||||||
|
<SelectItem value="none" disabled>
|
||||||
|
None
|
||||||
|
</SelectItem>
|
||||||
|
</SelectContent>
|
||||||
|
</Select>
|
||||||
|
<FormDescription>
|
||||||
|
Select the email provider to send the invitation
|
||||||
|
</FormDescription>
|
||||||
|
<FormMessage />
|
||||||
|
</FormItem>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
<DialogFooter className="flex w-full flex-row">
|
<DialogFooter className="flex w-full flex-row">
|
||||||
<Button
|
<Button
|
||||||
isLoading={isLoading}
|
isLoading={isLoading}
|
||||||
|
|||||||
3
apps/dokploy/drizzle/0093_nice_gorilla_man.sql
Normal file
3
apps/dokploy/drizzle/0093_nice_gorilla_man.sql
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
ALTER TABLE "user_temp" ALTER COLUMN "logCleanupCron" SET DEFAULT '0 0 * * *';
|
||||||
|
|
||||||
|
UPDATE "user_temp" SET "logCleanupCron" = '0 0 * * *' WHERE "logCleanupCron" IS NULL;
|
||||||
5718
apps/dokploy/drizzle/meta/0093_snapshot.json
Normal file
5718
apps/dokploy/drizzle/meta/0093_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -652,6 +652,13 @@
|
|||||||
"when": 1747713229160,
|
"when": 1747713229160,
|
||||||
"tag": "0092_stiff_the_watchers",
|
"tag": "0092_stiff_the_watchers",
|
||||||
"breakpoints": true
|
"breakpoints": true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idx": 93,
|
||||||
|
"version": "7",
|
||||||
|
"when": 1750397258622,
|
||||||
|
"tag": "0093_nice_gorilla_man",
|
||||||
|
"breakpoints": true
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
@@ -74,12 +74,7 @@ const Service = (
|
|||||||
}
|
}
|
||||||
}, [router.query.tab]);
|
}, [router.query.tab]);
|
||||||
|
|
||||||
const { data } = api.compose.one.useQuery(
|
const { data } = api.compose.one.useQuery({ composeId });
|
||||||
{ composeId },
|
|
||||||
{
|
|
||||||
refetchInterval: 5000,
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const { data: auth } = api.user.get.useQuery();
|
const { data: auth } = api.user.get.useQuery();
|
||||||
const { data: isCloud } = api.settings.isCloud.useQuery();
|
const { data: isCloud } = api.settings.isCloud.useQuery();
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import {
|
|||||||
findPostgresByBackupId,
|
findPostgresByBackupId,
|
||||||
findPostgresById,
|
findPostgresById,
|
||||||
findServerById,
|
findServerById,
|
||||||
|
keepLatestNBackups,
|
||||||
removeBackupById,
|
removeBackupById,
|
||||||
removeScheduleBackup,
|
removeScheduleBackup,
|
||||||
runMariadbBackup,
|
runMariadbBackup,
|
||||||
@@ -197,6 +198,8 @@ export const backupRouter = createTRPCRouter({
|
|||||||
const backup = await findBackupById(input.backupId);
|
const backup = await findBackupById(input.backupId);
|
||||||
const postgres = await findPostgresByBackupId(backup.backupId);
|
const postgres = await findPostgresByBackupId(backup.backupId);
|
||||||
await runPostgresBackup(postgres, backup);
|
await runPostgresBackup(postgres, backup);
|
||||||
|
|
||||||
|
await keepLatestNBackups(backup, postgres?.serverId);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const message =
|
const message =
|
||||||
@@ -217,6 +220,7 @@ export const backupRouter = createTRPCRouter({
|
|||||||
const backup = await findBackupById(input.backupId);
|
const backup = await findBackupById(input.backupId);
|
||||||
const mysql = await findMySqlByBackupId(backup.backupId);
|
const mysql = await findMySqlByBackupId(backup.backupId);
|
||||||
await runMySqlBackup(mysql, backup);
|
await runMySqlBackup(mysql, backup);
|
||||||
|
await keepLatestNBackups(backup, mysql?.serverId);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -233,6 +237,7 @@ export const backupRouter = createTRPCRouter({
|
|||||||
const backup = await findBackupById(input.backupId);
|
const backup = await findBackupById(input.backupId);
|
||||||
const mariadb = await findMariadbByBackupId(backup.backupId);
|
const mariadb = await findMariadbByBackupId(backup.backupId);
|
||||||
await runMariadbBackup(mariadb, backup);
|
await runMariadbBackup(mariadb, backup);
|
||||||
|
await keepLatestNBackups(backup, mariadb?.serverId);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -249,6 +254,7 @@ export const backupRouter = createTRPCRouter({
|
|||||||
const backup = await findBackupById(input.backupId);
|
const backup = await findBackupById(input.backupId);
|
||||||
const compose = await findComposeByBackupId(backup.backupId);
|
const compose = await findComposeByBackupId(backup.backupId);
|
||||||
await runComposeBackup(compose, backup);
|
await runComposeBackup(compose, backup);
|
||||||
|
await keepLatestNBackups(backup, compose?.serverId);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
@@ -265,6 +271,7 @@ export const backupRouter = createTRPCRouter({
|
|||||||
const backup = await findBackupById(input.backupId);
|
const backup = await findBackupById(input.backupId);
|
||||||
const mongo = await findMongoByBackupId(backup.backupId);
|
const mongo = await findMongoByBackupId(backup.backupId);
|
||||||
await runMongoBackup(mongo, backup);
|
await runMongoBackup(mongo, backup);
|
||||||
|
await keepLatestNBackups(backup, mongo?.serverId);
|
||||||
return true;
|
return true;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new TRPCError({
|
throw new TRPCError({
|
||||||
|
|||||||
@@ -496,7 +496,7 @@ export const composeRouter = createTRPCRouter({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return compose;
|
||||||
}),
|
}),
|
||||||
|
|
||||||
templates: publicProcedure
|
templates: publicProcedure
|
||||||
|
|||||||
@@ -446,4 +446,12 @@ export const notificationRouter = createTRPCRouter({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
|
getEmailProviders: adminProcedure.query(async ({ ctx }) => {
|
||||||
|
return await db.query.notifications.findMany({
|
||||||
|
where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
|
||||||
|
with: {
|
||||||
|
email: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -825,6 +825,9 @@ export const settingsRouter = createTRPCRouter({
|
|||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.mutation(async ({ input }) => {
|
.mutation(async ({ input }) => {
|
||||||
|
if (IS_CLOUD) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
if (input.cronExpression) {
|
if (input.cronExpression) {
|
||||||
return startLogCleanup(input.cronExpression);
|
return startLogCleanup(input.cronExpression);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
import {
|
import {
|
||||||
IS_CLOUD,
|
IS_CLOUD,
|
||||||
createApiKey,
|
createApiKey,
|
||||||
|
findAdmin,
|
||||||
|
findNotificationById,
|
||||||
findOrganizationById,
|
findOrganizationById,
|
||||||
findUserById,
|
findUserById,
|
||||||
getUserByToken,
|
getUserByToken,
|
||||||
removeUserById,
|
removeUserById,
|
||||||
|
sendEmailNotification,
|
||||||
updateUser,
|
updateUser,
|
||||||
} from "@dokploy/server";
|
} from "@dokploy/server";
|
||||||
import { db } from "@dokploy/server/db";
|
import { db } from "@dokploy/server/db";
|
||||||
@@ -362,4 +365,59 @@ export const userRouter = createTRPCRouter({
|
|||||||
|
|
||||||
return organizations.length;
|
return organizations.length;
|
||||||
}),
|
}),
|
||||||
|
sendInvitation: adminProcedure
|
||||||
|
.input(
|
||||||
|
z.object({
|
||||||
|
invitationId: z.string().min(1),
|
||||||
|
notificationId: z.string().min(1),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
.mutation(async ({ input, ctx }) => {
|
||||||
|
if (IS_CLOUD) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const notification = await findNotificationById(input.notificationId);
|
||||||
|
|
||||||
|
const email = notification.email;
|
||||||
|
|
||||||
|
const currentInvitation = await db.query.invitation.findFirst({
|
||||||
|
where: eq(invitation.id, input.invitationId),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!email) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "NOT_FOUND",
|
||||||
|
message: "Email notification not found",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const admin = await findAdmin();
|
||||||
|
const host =
|
||||||
|
process.env.NODE_ENV === "development"
|
||||||
|
? "http://localhost:3000"
|
||||||
|
: admin.user.host;
|
||||||
|
const inviteLink = `${host}/invitation?token=${input.invitationId}`;
|
||||||
|
|
||||||
|
const organization = await findOrganizationById(
|
||||||
|
ctx.session.activeOrganizationId,
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await sendEmailNotification(
|
||||||
|
{
|
||||||
|
...email,
|
||||||
|
toAddresses: [currentInvitation?.email || ""],
|
||||||
|
},
|
||||||
|
"Invitation to join organization",
|
||||||
|
`
|
||||||
|
<p>You are invited to join ${organization?.name || "organization"} on Dokploy. Click the link to accept the invitation: <a href="${inviteLink}">Accept Invitation</a></p>
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return inviteLink;
|
||||||
|
}),
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -56,7 +56,7 @@ export const users_temp = pgTable("user_temp", {
|
|||||||
letsEncryptEmail: text("letsEncryptEmail"),
|
letsEncryptEmail: text("letsEncryptEmail"),
|
||||||
sshPrivateKey: text("sshPrivateKey"),
|
sshPrivateKey: text("sshPrivateKey"),
|
||||||
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
|
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
|
||||||
logCleanupCron: text("logCleanupCron"),
|
logCleanupCron: text("logCleanupCron").default("0 0 * * *"),
|
||||||
role: text("role").notNull().default("user"),
|
role: text("role").notNull().default("user"),
|
||||||
// Metrics
|
// Metrics
|
||||||
enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
|
enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
|
||||||
|
|||||||
@@ -18,9 +18,6 @@ const { handler, api } = betterAuth({
|
|||||||
provider: "pg",
|
provider: "pg",
|
||||||
schema: schema,
|
schema: schema,
|
||||||
}),
|
}),
|
||||||
logger: {
|
|
||||||
disabled: process.env.NODE_ENV === "production",
|
|
||||||
},
|
|
||||||
appName: "Dokploy",
|
appName: "Dokploy",
|
||||||
socialProviders: {
|
socialProviders: {
|
||||||
github: {
|
github: {
|
||||||
|
|||||||
634
packages/server/src/services/cdn.ts
Normal file
634
packages/server/src/services/cdn.ts
Normal file
@@ -0,0 +1,634 @@
|
|||||||
|
// CDN Provider Interface
|
||||||
|
export interface CDNProvider {
|
||||||
|
name: string;
|
||||||
|
displayName: string;
|
||||||
|
checkIp: (ip: string) => boolean;
|
||||||
|
warningMessage: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const isIPInCIDR = (ip: string, cidr: string): boolean => {
|
||||||
|
const [network, prefixLength] = cidr.split("/");
|
||||||
|
if (!network || !prefixLength) return false;
|
||||||
|
const prefix = Number.parseInt(prefixLength, 10);
|
||||||
|
|
||||||
|
// Convert IP addresses to 32-bit integers
|
||||||
|
const ipToInt = (ipStr: string): number => {
|
||||||
|
return (
|
||||||
|
ipStr
|
||||||
|
.split(".")
|
||||||
|
.reduce((acc, octet) => (acc << 8) + Number.parseInt(octet, 10), 0) >>>
|
||||||
|
0
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const ipInt = ipToInt(ip);
|
||||||
|
const networkInt = ipToInt(network);
|
||||||
|
const mask = (0xffffffff << (32 - prefix)) >>> 0;
|
||||||
|
|
||||||
|
return (ipInt & mask) === (networkInt & mask);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Cloudflare IP ranges
|
||||||
|
// https://www.cloudflare.com/ips-v4
|
||||||
|
const CLOUDFLARE_IP_RANGES = [
|
||||||
|
"173.245.48.0/20",
|
||||||
|
"103.21.244.0/22",
|
||||||
|
"103.22.200.0/22",
|
||||||
|
"103.31.4.0/22",
|
||||||
|
"141.101.64.0/18",
|
||||||
|
"108.162.192.0/18",
|
||||||
|
"190.93.240.0/20",
|
||||||
|
"188.114.96.0/20",
|
||||||
|
"197.234.240.0/22",
|
||||||
|
"198.41.128.0/17",
|
||||||
|
"162.158.0.0/15",
|
||||||
|
"104.16.0.0/13",
|
||||||
|
"104.24.0.0/14",
|
||||||
|
"172.64.0.0/13",
|
||||||
|
"131.0.72.0/22",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Fastly IP ranges
|
||||||
|
// https://api.fastly.com/public-ip-list
|
||||||
|
const FASTLY_IP_RANGES = [
|
||||||
|
"23.235.32.0/20",
|
||||||
|
"43.249.72.0/22",
|
||||||
|
"103.244.50.0/24",
|
||||||
|
"103.245.222.0/23",
|
||||||
|
"103.245.224.0/24",
|
||||||
|
"104.156.80.0/20",
|
||||||
|
"140.248.64.0/18",
|
||||||
|
"140.248.128.0/17",
|
||||||
|
"146.75.0.0/17",
|
||||||
|
"151.101.0.0/16",
|
||||||
|
"157.52.64.0/18",
|
||||||
|
"167.82.0.0/17",
|
||||||
|
"167.82.128.0/20",
|
||||||
|
"167.82.160.0/20",
|
||||||
|
"167.82.224.0/20",
|
||||||
|
"172.111.64.0/18",
|
||||||
|
"185.31.16.0/22",
|
||||||
|
"199.27.72.0/21",
|
||||||
|
"199.232.0.0/16",
|
||||||
|
];
|
||||||
|
|
||||||
|
// Bunny CDN IP addresses
|
||||||
|
// https://bunnycdn.com/api/system/edgeserverlist
|
||||||
|
const BUNNY_CDN_IPS = new Set([
|
||||||
|
"89.187.188.227",
|
||||||
|
"89.187.188.228",
|
||||||
|
"139.180.134.196",
|
||||||
|
"89.38.96.158",
|
||||||
|
"89.187.162.249",
|
||||||
|
"89.187.162.242",
|
||||||
|
"185.102.217.65",
|
||||||
|
"185.93.1.243",
|
||||||
|
"156.146.40.49",
|
||||||
|
"185.59.220.199",
|
||||||
|
"185.59.220.198",
|
||||||
|
"195.181.166.158",
|
||||||
|
"185.180.12.68",
|
||||||
|
"138.199.24.209",
|
||||||
|
"138.199.24.211",
|
||||||
|
"79.127.216.111",
|
||||||
|
"79.127.216.112",
|
||||||
|
"89.187.169.47",
|
||||||
|
"138.199.24.218",
|
||||||
|
"185.40.106.117",
|
||||||
|
"200.25.45.4",
|
||||||
|
"200.25.57.5",
|
||||||
|
"193.162.131.1",
|
||||||
|
"200.25.11.8",
|
||||||
|
"200.25.53.5",
|
||||||
|
"200.25.13.98",
|
||||||
|
"41.242.2.18",
|
||||||
|
"200.25.62.5",
|
||||||
|
"200.25.38.69",
|
||||||
|
"200.25.42.70",
|
||||||
|
"200.25.36.166",
|
||||||
|
"195.206.229.106",
|
||||||
|
"194.242.11.186",
|
||||||
|
"185.164.35.8",
|
||||||
|
"94.20.154.22",
|
||||||
|
"185.93.1.244",
|
||||||
|
"156.59.145.154",
|
||||||
|
"143.244.49.177",
|
||||||
|
"138.199.46.66",
|
||||||
|
"138.199.37.227",
|
||||||
|
"138.199.37.231",
|
||||||
|
"138.199.37.230",
|
||||||
|
"138.199.37.229",
|
||||||
|
"138.199.46.69",
|
||||||
|
"138.199.46.68",
|
||||||
|
"138.199.46.67",
|
||||||
|
"185.93.1.246",
|
||||||
|
"138.199.37.232",
|
||||||
|
"195.181.163.196",
|
||||||
|
"107.182.163.162",
|
||||||
|
"195.181.163.195",
|
||||||
|
"84.17.46.53",
|
||||||
|
"212.102.40.114",
|
||||||
|
"84.17.46.54",
|
||||||
|
"138.199.40.58",
|
||||||
|
"143.244.38.134",
|
||||||
|
"143.244.38.136",
|
||||||
|
"185.152.64.17",
|
||||||
|
"84.17.59.115",
|
||||||
|
"89.187.165.194",
|
||||||
|
"138.199.15.193",
|
||||||
|
"89.35.237.170",
|
||||||
|
"37.19.216.130",
|
||||||
|
"185.93.1.247",
|
||||||
|
"185.93.3.244",
|
||||||
|
"143.244.49.179",
|
||||||
|
"143.244.49.180",
|
||||||
|
"138.199.9.104",
|
||||||
|
"185.152.66.243",
|
||||||
|
"143.244.49.178",
|
||||||
|
"169.150.221.147",
|
||||||
|
"200.25.18.73",
|
||||||
|
"84.17.63.178",
|
||||||
|
"200.25.32.131",
|
||||||
|
"37.19.207.34",
|
||||||
|
"192.189.65.146",
|
||||||
|
"143.244.45.177",
|
||||||
|
"185.93.1.249",
|
||||||
|
"185.93.1.250",
|
||||||
|
"169.150.215.115",
|
||||||
|
"209.177.87.197",
|
||||||
|
"156.146.56.162",
|
||||||
|
"156.146.56.161",
|
||||||
|
"185.93.2.246",
|
||||||
|
"185.93.2.245",
|
||||||
|
"212.102.40.113",
|
||||||
|
"185.93.2.244",
|
||||||
|
"143.244.50.82",
|
||||||
|
"143.244.50.83",
|
||||||
|
"156.146.56.163",
|
||||||
|
"129.227.217.178",
|
||||||
|
"129.227.217.179",
|
||||||
|
"200.25.69.94",
|
||||||
|
"128.1.52.179",
|
||||||
|
"200.25.16.103",
|
||||||
|
"15.235.54.226",
|
||||||
|
"102.67.138.155",
|
||||||
|
"156.146.43.65",
|
||||||
|
"195.181.163.203",
|
||||||
|
"195.181.163.202",
|
||||||
|
"156.146.56.169",
|
||||||
|
"156.146.56.170",
|
||||||
|
"156.146.56.166",
|
||||||
|
"156.146.56.171",
|
||||||
|
"169.150.207.210",
|
||||||
|
"156.146.56.167",
|
||||||
|
"143.244.50.84",
|
||||||
|
"143.244.50.85",
|
||||||
|
"143.244.50.86",
|
||||||
|
"143.244.50.87",
|
||||||
|
"156.146.56.168",
|
||||||
|
"169.150.207.211",
|
||||||
|
"212.102.50.59",
|
||||||
|
"146.185.248.15",
|
||||||
|
"143.244.50.90",
|
||||||
|
"143.244.50.91",
|
||||||
|
"143.244.50.88",
|
||||||
|
"143.244.50.209",
|
||||||
|
"143.244.50.213",
|
||||||
|
"143.244.50.214",
|
||||||
|
"143.244.49.183",
|
||||||
|
"143.244.50.89",
|
||||||
|
"143.244.50.210",
|
||||||
|
"143.244.50.211",
|
||||||
|
"143.244.50.212",
|
||||||
|
"5.42.206.66",
|
||||||
|
"94.46.27.186",
|
||||||
|
"169.150.207.213",
|
||||||
|
"169.150.207.214",
|
||||||
|
"169.150.207.215",
|
||||||
|
"169.150.207.212",
|
||||||
|
"169.150.219.114",
|
||||||
|
"169.150.202.210",
|
||||||
|
"169.150.242.193",
|
||||||
|
"185.93.1.251",
|
||||||
|
"169.150.207.216",
|
||||||
|
"169.150.207.217",
|
||||||
|
"169.150.238.19",
|
||||||
|
"102.219.126.20",
|
||||||
|
"156.59.66.182",
|
||||||
|
"122.10.251.130",
|
||||||
|
"185.24.11.18",
|
||||||
|
"138.199.36.7",
|
||||||
|
"138.199.36.8",
|
||||||
|
"138.199.36.9",
|
||||||
|
"138.199.36.10",
|
||||||
|
"138.199.36.11",
|
||||||
|
"138.199.37.225",
|
||||||
|
"84.17.46.49",
|
||||||
|
"84.17.37.217",
|
||||||
|
"169.150.225.35",
|
||||||
|
"169.150.225.36",
|
||||||
|
"169.150.225.37",
|
||||||
|
"169.150.225.38",
|
||||||
|
"169.150.225.39",
|
||||||
|
"169.150.225.34",
|
||||||
|
"169.150.236.97",
|
||||||
|
"169.150.236.98",
|
||||||
|
"169.150.236.99",
|
||||||
|
"169.150.236.100",
|
||||||
|
"93.189.63.149",
|
||||||
|
"143.244.56.49",
|
||||||
|
"143.244.56.50",
|
||||||
|
"143.244.56.51",
|
||||||
|
"169.150.247.40",
|
||||||
|
"169.150.247.33",
|
||||||
|
"169.150.247.34",
|
||||||
|
"169.150.247.35",
|
||||||
|
"169.150.247.36",
|
||||||
|
"169.150.247.37",
|
||||||
|
"169.150.247.38",
|
||||||
|
"169.150.247.39",
|
||||||
|
"38.142.94.218",
|
||||||
|
"87.249.137.52",
|
||||||
|
"38.104.169.186",
|
||||||
|
"66.181.163.74",
|
||||||
|
"84.17.38.227",
|
||||||
|
"84.17.38.228",
|
||||||
|
"84.17.38.229",
|
||||||
|
"84.17.38.230",
|
||||||
|
"84.17.38.231",
|
||||||
|
"84.17.38.232",
|
||||||
|
"169.150.225.41",
|
||||||
|
"169.150.225.42",
|
||||||
|
"169.150.249.162",
|
||||||
|
"169.150.249.163",
|
||||||
|
"169.150.249.164",
|
||||||
|
"169.150.249.165",
|
||||||
|
"169.150.249.166",
|
||||||
|
"169.150.249.167",
|
||||||
|
"169.150.249.168",
|
||||||
|
"169.150.249.169",
|
||||||
|
"185.131.64.124",
|
||||||
|
"103.112.0.22",
|
||||||
|
"37.236.234.2",
|
||||||
|
"169.150.252.209",
|
||||||
|
"212.102.46.118",
|
||||||
|
"192.169.120.162",
|
||||||
|
"93.180.217.214",
|
||||||
|
"37.19.203.178",
|
||||||
|
"107.155.47.146",
|
||||||
|
"193.201.190.174",
|
||||||
|
"156.59.95.218",
|
||||||
|
"213.170.143.139",
|
||||||
|
"129.227.186.154",
|
||||||
|
"195.238.127.98",
|
||||||
|
"200.25.22.6",
|
||||||
|
"204.16.244.92",
|
||||||
|
"200.25.70.101",
|
||||||
|
"200.25.66.100",
|
||||||
|
"139.180.209.182",
|
||||||
|
"103.108.231.41",
|
||||||
|
"103.108.229.5",
|
||||||
|
"103.216.220.9",
|
||||||
|
"169.150.225.40",
|
||||||
|
"212.102.50.49",
|
||||||
|
"212.102.50.52",
|
||||||
|
"109.61.83.242",
|
||||||
|
"109.61.83.243",
|
||||||
|
"212.102.50.50",
|
||||||
|
"169.150.225.43",
|
||||||
|
"45.125.247.57",
|
||||||
|
"103.235.199.170",
|
||||||
|
"128.1.35.170",
|
||||||
|
"38.32.110.58",
|
||||||
|
"169.150.220.228",
|
||||||
|
"169.150.220.229",
|
||||||
|
"169.150.220.230",
|
||||||
|
"169.150.220.231",
|
||||||
|
"138.199.4.179",
|
||||||
|
"207.211.214.145",
|
||||||
|
"109.61.86.193",
|
||||||
|
"103.214.20.95",
|
||||||
|
"178.175.134.51",
|
||||||
|
"138.199.4.178",
|
||||||
|
"172.255.253.140",
|
||||||
|
"185.24.11.19",
|
||||||
|
"109.61.83.244",
|
||||||
|
"109.61.83.245",
|
||||||
|
"84.17.38.250",
|
||||||
|
"84.17.38.251",
|
||||||
|
"146.59.69.202",
|
||||||
|
"146.70.80.218",
|
||||||
|
"200.25.80.74",
|
||||||
|
"79.127.213.214",
|
||||||
|
"79.127.213.215",
|
||||||
|
"79.127.213.216",
|
||||||
|
"79.127.213.217",
|
||||||
|
"195.69.140.112",
|
||||||
|
"109.61.83.247",
|
||||||
|
"109.61.83.246",
|
||||||
|
"185.93.2.248",
|
||||||
|
"109.61.83.249",
|
||||||
|
"109.61.83.250",
|
||||||
|
"109.61.83.251",
|
||||||
|
"46.199.75.115",
|
||||||
|
"141.164.35.160",
|
||||||
|
"109.61.83.97",
|
||||||
|
"109.61.83.98",
|
||||||
|
"109.61.83.99",
|
||||||
|
"129.227.179.18",
|
||||||
|
"185.180.14.250",
|
||||||
|
"152.89.160.26",
|
||||||
|
"5.189.202.62",
|
||||||
|
"98.98.242.142",
|
||||||
|
"156.59.92.126",
|
||||||
|
"84.17.59.117",
|
||||||
|
"79.127.216.66",
|
||||||
|
"79.127.204.113",
|
||||||
|
"79.127.237.132",
|
||||||
|
"169.150.236.104",
|
||||||
|
"169.150.236.105",
|
||||||
|
"37.27.135.61",
|
||||||
|
"158.51.123.205",
|
||||||
|
"156.146.43.70",
|
||||||
|
"156.146.43.71",
|
||||||
|
"156.146.43.72",
|
||||||
|
"180.149.231.175",
|
||||||
|
"185.93.2.243",
|
||||||
|
"143.244.56.52",
|
||||||
|
"143.244.56.53",
|
||||||
|
"143.244.56.54",
|
||||||
|
"143.244.56.55",
|
||||||
|
"143.244.56.56",
|
||||||
|
"143.244.56.57",
|
||||||
|
"143.244.56.58",
|
||||||
|
"144.76.236.44",
|
||||||
|
"88.198.57.50",
|
||||||
|
"78.46.69.199",
|
||||||
|
"136.243.16.49",
|
||||||
|
"138.201.86.122",
|
||||||
|
"136.243.42.90",
|
||||||
|
"88.99.95.221",
|
||||||
|
"178.63.2.112",
|
||||||
|
"5.9.98.45",
|
||||||
|
"136.243.42.10",
|
||||||
|
"169.150.236.106",
|
||||||
|
"169.150.236.107",
|
||||||
|
"185.93.1.242",
|
||||||
|
"185.93.1.245",
|
||||||
|
"143.244.60.193",
|
||||||
|
"195.181.163.194",
|
||||||
|
"79.127.188.193",
|
||||||
|
"79.127.188.196",
|
||||||
|
"79.127.188.194",
|
||||||
|
"79.127.188.195",
|
||||||
|
"104.166.144.106",
|
||||||
|
"156.59.126.78",
|
||||||
|
"185.135.85.154",
|
||||||
|
"38.54.5.37",
|
||||||
|
"38.54.3.92",
|
||||||
|
"185.165.170.74",
|
||||||
|
"207.121.80.118",
|
||||||
|
"207.121.46.228",
|
||||||
|
"207.121.46.236",
|
||||||
|
"207.121.46.244",
|
||||||
|
"207.121.46.252",
|
||||||
|
"216.202.235.164",
|
||||||
|
"207.121.46.220",
|
||||||
|
"207.121.75.132",
|
||||||
|
"207.121.80.12",
|
||||||
|
"207.121.80.172",
|
||||||
|
"207.121.90.60",
|
||||||
|
"207.121.90.68",
|
||||||
|
"207.121.97.204",
|
||||||
|
"207.121.90.252",
|
||||||
|
"207.121.97.236",
|
||||||
|
"207.121.99.12",
|
||||||
|
"138.199.24.219",
|
||||||
|
"185.93.2.251",
|
||||||
|
"138.199.46.65",
|
||||||
|
"207.121.41.196",
|
||||||
|
"207.121.99.20",
|
||||||
|
"207.121.99.36",
|
||||||
|
"207.121.99.44",
|
||||||
|
"207.121.99.52",
|
||||||
|
"207.121.99.60",
|
||||||
|
"207.121.23.68",
|
||||||
|
"207.121.23.124",
|
||||||
|
"207.121.23.244",
|
||||||
|
"207.121.23.180",
|
||||||
|
"207.121.23.188",
|
||||||
|
"207.121.23.196",
|
||||||
|
"207.121.23.204",
|
||||||
|
"207.121.24.52",
|
||||||
|
"207.121.24.60",
|
||||||
|
"207.121.24.68",
|
||||||
|
"207.121.24.76",
|
||||||
|
"207.121.24.92",
|
||||||
|
"207.121.24.100",
|
||||||
|
"207.121.24.108",
|
||||||
|
"207.121.24.116",
|
||||||
|
"154.95.86.76",
|
||||||
|
"5.9.99.73",
|
||||||
|
"78.46.92.118",
|
||||||
|
"144.76.65.213",
|
||||||
|
"78.46.156.89",
|
||||||
|
"88.198.9.155",
|
||||||
|
"144.76.79.22",
|
||||||
|
"103.1.215.93",
|
||||||
|
"103.137.12.33",
|
||||||
|
"103.107.196.31",
|
||||||
|
"116.90.72.155",
|
||||||
|
"103.137.14.5",
|
||||||
|
"116.90.75.65",
|
||||||
|
"37.19.207.37",
|
||||||
|
"208.83.234.224",
|
||||||
|
"116.202.155.146",
|
||||||
|
"116.202.193.178",
|
||||||
|
"116.202.224.168",
|
||||||
|
"188.40.126.227",
|
||||||
|
"88.99.26.189",
|
||||||
|
"168.119.39.238",
|
||||||
|
"88.99.26.97",
|
||||||
|
"168.119.12.188",
|
||||||
|
"176.9.139.55",
|
||||||
|
"142.132.223.79",
|
||||||
|
"142.132.223.80",
|
||||||
|
"142.132.223.81",
|
||||||
|
"46.4.116.17",
|
||||||
|
"46.4.119.81",
|
||||||
|
"167.235.114.167",
|
||||||
|
"159.69.68.171",
|
||||||
|
"178.63.21.52",
|
||||||
|
"46.4.120.152",
|
||||||
|
"116.202.80.247",
|
||||||
|
"5.9.71.119",
|
||||||
|
"195.201.11.156",
|
||||||
|
"78.46.123.17",
|
||||||
|
"46.4.113.143",
|
||||||
|
"136.243.2.236",
|
||||||
|
"195.201.81.217",
|
||||||
|
"148.251.42.123",
|
||||||
|
"94.130.68.122",
|
||||||
|
"88.198.22.103",
|
||||||
|
"46.4.102.90",
|
||||||
|
"157.90.180.205",
|
||||||
|
"162.55.135.11",
|
||||||
|
"195.201.109.59",
|
||||||
|
"148.251.41.244",
|
||||||
|
"116.202.235.16",
|
||||||
|
"128.140.70.141",
|
||||||
|
"78.46.74.86",
|
||||||
|
"78.46.74.85",
|
||||||
|
"178.63.41.242",
|
||||||
|
"178.63.41.247",
|
||||||
|
"178.63.41.234",
|
||||||
|
"104.237.53.74",
|
||||||
|
"104.237.54.154",
|
||||||
|
"104.237.51.58",
|
||||||
|
"64.185.235.90",
|
||||||
|
"64.185.234.114",
|
||||||
|
"64.185.232.194",
|
||||||
|
"64.185.232.178",
|
||||||
|
"64.185.232.82",
|
||||||
|
"103.60.15.169",
|
||||||
|
"103.60.15.170",
|
||||||
|
"103.60.15.171",
|
||||||
|
"103.60.15.172",
|
||||||
|
"103.60.15.173",
|
||||||
|
"103.60.15.162",
|
||||||
|
"103.60.15.163",
|
||||||
|
"103.60.15.164",
|
||||||
|
"103.60.15.165",
|
||||||
|
"103.60.15.166",
|
||||||
|
"103.60.15.167",
|
||||||
|
"103.60.15.168",
|
||||||
|
"109.248.43.116",
|
||||||
|
"109.248.43.117",
|
||||||
|
"109.248.43.162",
|
||||||
|
"109.248.43.163",
|
||||||
|
"109.248.43.164",
|
||||||
|
"109.248.43.165",
|
||||||
|
"49.12.71.27",
|
||||||
|
"49.12.0.158",
|
||||||
|
"78.47.94.156",
|
||||||
|
"109.248.43.159",
|
||||||
|
"109.248.43.160",
|
||||||
|
"109.248.43.208",
|
||||||
|
"109.248.43.179",
|
||||||
|
"109.248.43.232",
|
||||||
|
"109.248.43.231",
|
||||||
|
"109.248.43.241",
|
||||||
|
"109.248.43.236",
|
||||||
|
"109.248.43.240",
|
||||||
|
"109.248.43.103",
|
||||||
|
"116.202.118.194",
|
||||||
|
"116.202.80.29",
|
||||||
|
"159.69.57.80",
|
||||||
|
"139.180.129.216",
|
||||||
|
"139.99.174.7",
|
||||||
|
"89.187.169.18",
|
||||||
|
"89.187.179.7",
|
||||||
|
"143.244.62.213",
|
||||||
|
"185.93.3.246",
|
||||||
|
"195.181.163.198",
|
||||||
|
"185.152.64.19",
|
||||||
|
"84.17.37.211",
|
||||||
|
"212.102.50.54",
|
||||||
|
"212.102.46.115",
|
||||||
|
"143.244.38.135",
|
||||||
|
"169.150.238.21",
|
||||||
|
"169.150.207.51",
|
||||||
|
"169.150.207.49",
|
||||||
|
"84.17.38.226",
|
||||||
|
"84.17.38.225",
|
||||||
|
"37.19.222.248",
|
||||||
|
"37.19.222.249",
|
||||||
|
"169.150.247.139",
|
||||||
|
"169.150.247.177",
|
||||||
|
"169.150.247.178",
|
||||||
|
"169.150.213.49",
|
||||||
|
"212.102.46.119",
|
||||||
|
"84.17.38.234",
|
||||||
|
"84.17.38.233",
|
||||||
|
"169.150.247.179",
|
||||||
|
"169.150.247.180",
|
||||||
|
"169.150.247.181",
|
||||||
|
"169.150.247.182",
|
||||||
|
"169.150.247.183",
|
||||||
|
"169.150.247.138",
|
||||||
|
"169.150.247.184",
|
||||||
|
"169.150.247.185",
|
||||||
|
"156.146.58.83",
|
||||||
|
"212.102.43.88",
|
||||||
|
"89.187.169.26",
|
||||||
|
"109.61.89.57",
|
||||||
|
"109.61.89.58",
|
||||||
|
"109.61.83.241",
|
||||||
|
"84.17.38.243",
|
||||||
|
"84.17.38.244",
|
||||||
|
"84.17.38.246",
|
||||||
|
"84.17.38.247",
|
||||||
|
"84.17.38.245",
|
||||||
|
"143.244.38.129",
|
||||||
|
"84.17.38.248",
|
||||||
|
"89.187.176.34",
|
||||||
|
"185.152.64.23",
|
||||||
|
"79.127.213.209",
|
||||||
|
"79.127.213.210",
|
||||||
|
"84.17.37.209",
|
||||||
|
"156.146.43.68",
|
||||||
|
"185.93.3.243",
|
||||||
|
"79.127.219.198",
|
||||||
|
"138.199.33.57",
|
||||||
|
"79.127.242.89",
|
||||||
|
"138.199.4.136",
|
||||||
|
"169.150.220.235",
|
||||||
|
"138.199.4.129",
|
||||||
|
"138.199.4.177",
|
||||||
|
"37.19.222.34",
|
||||||
|
"46.151.193.85",
|
||||||
|
"212.104.158.17",
|
||||||
|
"212.104.158.18",
|
||||||
|
"212.104.158.19",
|
||||||
|
"212.104.158.20",
|
||||||
|
"212.104.158.21",
|
||||||
|
"212.104.158.22",
|
||||||
|
"212.104.158.24",
|
||||||
|
"212.104.158.26",
|
||||||
|
"79.127.237.134",
|
||||||
|
"89.187.184.177",
|
||||||
|
"89.187.184.179",
|
||||||
|
"89.187.184.173",
|
||||||
|
"89.187.184.178",
|
||||||
|
"89.187.184.176",
|
||||||
|
]);
|
||||||
|
|
||||||
|
const CDN_PROVIDERS: CDNProvider[] = [
|
||||||
|
{
|
||||||
|
name: "cloudflare",
|
||||||
|
displayName: "Cloudflare",
|
||||||
|
checkIp: (ip: string) =>
|
||||||
|
CLOUDFLARE_IP_RANGES.some((range) => isIPInCIDR(ip, range)),
|
||||||
|
warningMessage:
|
||||||
|
"Domain is behind Cloudflare - actual IP is masked by Cloudflare proxy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "bunnycdn",
|
||||||
|
displayName: "Bunny CDN",
|
||||||
|
checkIp: (ip: string) => BUNNY_CDN_IPS.has(ip),
|
||||||
|
warningMessage:
|
||||||
|
"Domain is behind Bunny CDN - actual IP is masked by CDN proxy",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "fastly",
|
||||||
|
displayName: "Fastly",
|
||||||
|
checkIp: (ip: string) =>
|
||||||
|
FASTLY_IP_RANGES.some((range) => isIPInCIDR(ip, range)),
|
||||||
|
warningMessage:
|
||||||
|
"Domain is behind Fastly - actual IP is masked by CDN proxy",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const detectCDNProvider = (ip: string): CDNProvider | null => {
|
||||||
|
return CDN_PROVIDERS.find((provider) => provider.checkIp(ip)) || null;
|
||||||
|
};
|
||||||
@@ -8,6 +8,7 @@ import { eq } from "drizzle-orm";
|
|||||||
import { type apiCreateDomain, domains } from "../db/schema";
|
import { type apiCreateDomain, domains } from "../db/schema";
|
||||||
import { findUserById } from "./admin";
|
import { findUserById } from "./admin";
|
||||||
import { findApplicationById } from "./application";
|
import { findApplicationById } from "./application";
|
||||||
|
import { detectCDNProvider } from "./cdn";
|
||||||
import { findServerById } from "./server";
|
import { findServerById } from "./server";
|
||||||
|
|
||||||
export type Domain = typeof domains.$inferSelect;
|
export type Domain = typeof domains.$inferSelect;
|
||||||
@@ -142,28 +143,6 @@ export const getDomainHost = (domain: Domain) => {
|
|||||||
|
|
||||||
const resolveDns = promisify(dns.resolve4);
|
const resolveDns = promisify(dns.resolve4);
|
||||||
|
|
||||||
// Cloudflare IP ranges (simplified - these are some common ones)
|
|
||||||
const CLOUDFLARE_IPS = [
|
|
||||||
"172.67.",
|
|
||||||
"104.21.",
|
|
||||||
"104.16.",
|
|
||||||
"104.17.",
|
|
||||||
"104.18.",
|
|
||||||
"104.19.",
|
|
||||||
"104.20.",
|
|
||||||
"104.22.",
|
|
||||||
"104.23.",
|
|
||||||
"104.24.",
|
|
||||||
"104.25.",
|
|
||||||
"104.26.",
|
|
||||||
"104.27.",
|
|
||||||
"104.28.",
|
|
||||||
];
|
|
||||||
|
|
||||||
const isCloudflareIp = (ip: string) => {
|
|
||||||
return CLOUDFLARE_IPS.some((range) => ip.startsWith(range));
|
|
||||||
};
|
|
||||||
|
|
||||||
export const validateDomain = async (
|
export const validateDomain = async (
|
||||||
domain: string,
|
domain: string,
|
||||||
expectedIp?: string,
|
expectedIp?: string,
|
||||||
@@ -172,6 +151,7 @@ export const validateDomain = async (
|
|||||||
resolvedIp?: string;
|
resolvedIp?: string;
|
||||||
error?: string;
|
error?: string;
|
||||||
isCloudflare?: boolean;
|
isCloudflare?: boolean;
|
||||||
|
cdnProvider?: string;
|
||||||
}> => {
|
}> => {
|
||||||
try {
|
try {
|
||||||
// Remove protocol and path if present
|
// Remove protocol and path if present
|
||||||
@@ -182,17 +162,18 @@ export const validateDomain = async (
|
|||||||
|
|
||||||
const resolvedIps = ips.map((ip) => ip.toString());
|
const resolvedIps = ips.map((ip) => ip.toString());
|
||||||
|
|
||||||
// Check if it's a Cloudflare IP
|
// Check if any IP belongs to a CDN provider
|
||||||
const behindCloudflare = ips.some((ip) => isCloudflareIp(ip));
|
const cdnProvider = ips
|
||||||
|
.map((ip) => detectCDNProvider(ip))
|
||||||
|
.find((provider) => provider !== null);
|
||||||
|
|
||||||
// If behind Cloudflare, we consider it valid but inform the user
|
// If behind a CDN, we consider it valid but inform the user
|
||||||
if (behindCloudflare) {
|
if (cdnProvider) {
|
||||||
return {
|
return {
|
||||||
isValid: true,
|
isValid: true,
|
||||||
resolvedIp: resolvedIps.join(", "),
|
resolvedIp: resolvedIps.join(", "),
|
||||||
isCloudflare: true,
|
cdnProvider: cdnProvider.displayName,
|
||||||
error:
|
error: cdnProvider.warningMessage,
|
||||||
"Domain is behind Cloudflare - actual IP is masked by Cloudflare proxy",
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -139,6 +139,8 @@ export const initializeTraefik = async ({
|
|||||||
const newContainer = docker.getContainer(containerName);
|
const newContainer = docker.getContainer(containerName);
|
||||||
await newContainer.start();
|
await newContainer.start();
|
||||||
console.log("Traefik container started successfully after retry");
|
console.log("Traefik container started successfully after retry");
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -37,7 +37,8 @@ export const startLogCleanup = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
} catch (_) {
|
} catch (error) {
|
||||||
|
console.error("Error starting log cleanup:", error);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,6 +88,7 @@ export const initCronJobs = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (admin?.user.logCleanupCron) {
|
if (admin?.user.logCleanupCron) {
|
||||||
|
console.log("Starting log requests cleanup", admin.user.logCleanupCron);
|
||||||
await startLogCleanup(admin.user.logCleanupCron);
|
await startLogCleanup(admin.user.logCleanupCron);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,7 +2,10 @@ import { createHash } from "node:crypto";
|
|||||||
import type { WriteStream } from "node:fs";
|
import type { WriteStream } from "node:fs";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import type { ApplicationNested } from ".";
|
import type { ApplicationNested } from ".";
|
||||||
import { prepareEnvironmentVariables } from "../docker/utils";
|
import {
|
||||||
|
parseEnvironmentKeyValuePair,
|
||||||
|
prepareEnvironmentVariables,
|
||||||
|
} from "../docker/utils";
|
||||||
import { getBuildAppDirectory } from "../filesystem/directory";
|
import { getBuildAppDirectory } from "../filesystem/directory";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
import { spawnAsync } from "../process/spawnAsync";
|
import { spawnAsync } from "../process/spawnAsync";
|
||||||
@@ -81,10 +84,10 @@ export const buildRailpack = async (
|
|||||||
|
|
||||||
// Add secrets properly formatted
|
// Add secrets properly formatted
|
||||||
const env: { [key: string]: string } = {};
|
const env: { [key: string]: string } = {};
|
||||||
for (const envVar of envVariables) {
|
for (const pair of envVariables) {
|
||||||
const [key, value] = envVar.split("=");
|
const [key, value] = parseEnvironmentKeyValuePair(pair);
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
buildArgs.push("--secret", `id=${key},env='${key}'`);
|
buildArgs.push("--secret", `id=${key},env=${key}`);
|
||||||
env[key] = value;
|
env[key] = value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -161,11 +164,11 @@ export const getRailpackCommand = (
|
|||||||
|
|
||||||
// Add secrets properly formatted
|
// Add secrets properly formatted
|
||||||
const exportEnvs = [];
|
const exportEnvs = [];
|
||||||
for (const envVar of envVariables) {
|
for (const pair of envVariables) {
|
||||||
const [key, value] = envVar.split("=");
|
const [key, value] = parseEnvironmentKeyValuePair(pair);
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
buildArgs.push("--secret", `id=${key},env='${key}'`);
|
buildArgs.push("--secret", `id=${key},env=${key}`);
|
||||||
exportEnvs.push(`export ${key}=${value}`);
|
exportEnvs.push(`export ${key}='${value}'`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -279,6 +279,17 @@ export const prepareEnvironmentVariables = (
|
|||||||
return resolvedVars;
|
return resolvedVars;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const parseEnvironmentKeyValuePair = (
|
||||||
|
pair: string,
|
||||||
|
): [string, string] => {
|
||||||
|
const [key, ...valueParts] = pair.split("=");
|
||||||
|
if (!key || !valueParts.length) {
|
||||||
|
throw new Error(`Invalid environment variable pair: ${pair}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [key, valueParts.join("")];
|
||||||
|
};
|
||||||
|
|
||||||
export const getEnviromentVariablesObject = (
|
export const getEnviromentVariablesObject = (
|
||||||
input: string | null,
|
input: string | null,
|
||||||
projectEnv?: string | null,
|
projectEnv?: string | null,
|
||||||
@@ -288,7 +299,7 @@ export const getEnviromentVariablesObject = (
|
|||||||
const jsonObject: Record<string, string> = {};
|
const jsonObject: Record<string, string> = {};
|
||||||
|
|
||||||
for (const pair of envs) {
|
for (const pair of envs) {
|
||||||
const [key, value] = pair.split("=");
|
const [key, value] = parseEnvironmentKeyValuePair(pair);
|
||||||
if (key && value) {
|
if (key && value) {
|
||||||
jsonObject[key] = value;
|
jsonObject[key] = value;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const cloneBitbucketRepository = async (
|
|||||||
});
|
});
|
||||||
writeStream.write(`\nCloned ${repoclone} to ${outputPath}: ✅\n`);
|
writeStream.write(`\nCloned ${repoclone} to ${outputPath}: ✅\n`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeStream.write(`ERROR Clonning: ${error}: ❌`);
|
writeStream.write(`ERROR Cloning: ${error}: ❌`);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
writeStream.end();
|
writeStream.end();
|
||||||
|
|||||||
@@ -232,7 +232,7 @@ export const cloneGiteaRepository = async (
|
|||||||
);
|
);
|
||||||
writeStream.write(`\nCloned ${repoClone}: ✅\n`);
|
writeStream.write(`\nCloned ${repoClone}: ✅\n`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeStream.write(`ERROR Clonning: ${error}: ❌`);
|
writeStream.write(`ERROR Cloning: ${error}: ❌`);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
writeStream.end();
|
writeStream.end();
|
||||||
|
|||||||
@@ -149,7 +149,7 @@ export const cloneGithubRepository = async ({
|
|||||||
});
|
});
|
||||||
writeStream.write(`\nCloned ${repoclone}: ✅\n`);
|
writeStream.write(`\nCloned ${repoclone}: ✅\n`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeStream.write(`ERROR Clonning: ${error}: ❌`);
|
writeStream.write(`ERROR Cloning: ${error}: ❌`);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
writeStream.end();
|
writeStream.end();
|
||||||
|
|||||||
@@ -132,7 +132,7 @@ export const cloneGitlabRepository = async (
|
|||||||
const cloneUrl = `https://oauth2:${gitlab?.accessToken}@${repoclone}`;
|
const cloneUrl = `https://oauth2:${gitlab?.accessToken}@${repoclone}`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
writeStream.write(`\nClonning Repo ${repoclone} to ${outputPath}: ✅\n`);
|
writeStream.write(`\nCloning Repo ${repoclone} to ${outputPath}: ✅\n`);
|
||||||
const cloneArgs = [
|
const cloneArgs = [
|
||||||
"clone",
|
"clone",
|
||||||
"--branch",
|
"--branch",
|
||||||
@@ -152,7 +152,7 @@ export const cloneGitlabRepository = async (
|
|||||||
});
|
});
|
||||||
writeStream.write(`\nCloned ${repoclone}: ✅\n`);
|
writeStream.write(`\nCloned ${repoclone}: ✅\n`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
writeStream.write(`ERROR Clonning: ${error}: ❌`);
|
writeStream.write(`ERROR Cloning: ${error}: ❌`);
|
||||||
throw error;
|
throw error;
|
||||||
} finally {
|
} finally {
|
||||||
writeStream.end();
|
writeStream.end();
|
||||||
|
|||||||
Reference in New Issue
Block a user