mirror of
https://github.com/Dokploy/dokploy.git
synced 2026-06-15 20:25:23 +02:00
@@ -24,7 +24,6 @@ import {
|
||||
FormMessage,
|
||||
} from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { api } from "@/utils/api";
|
||||
|
||||
const organizationSchema = z.object({
|
||||
@@ -55,8 +54,6 @@ export function AddOrganization({ organizationId }: Props) {
|
||||
const { mutateAsync, isPending } = organizationId
|
||||
? api.organization.update.useMutation()
|
||||
: api.organization.create.useMutation();
|
||||
const { refetch: refetchActiveOrganization } =
|
||||
authClient.useActiveOrganization();
|
||||
|
||||
const form = useForm<OrganizationFormValues>({
|
||||
resolver: zodResolver(organizationSchema),
|
||||
@@ -89,7 +86,7 @@ export function AddOrganization({ organizationId }: Props) {
|
||||
utils.organization.all.invalidate();
|
||||
if (organizationId) {
|
||||
utils.organization.one.invalidate({ organizationId });
|
||||
refetchActiveOrganization();
|
||||
utils.organization.active.invalidate();
|
||||
}
|
||||
setOpen(false);
|
||||
})
|
||||
|
||||
@@ -17,7 +17,8 @@ import { api } from "@/utils/api";
|
||||
|
||||
export const AddGithubProvider = () => {
|
||||
const [isOpen, setIsOpen] = useState(false);
|
||||
const { data: activeOrganization } = authClient.useActiveOrganization();
|
||||
const { data: activeOrganization } = api.organization.active.useQuery();
|
||||
|
||||
const { data: session } = authClient.useSession();
|
||||
const { data } = api.user.get.useQuery();
|
||||
const [manifest, setManifest] = useState("");
|
||||
@@ -52,7 +53,7 @@ export const AddGithubProvider = () => {
|
||||
);
|
||||
|
||||
setManifest(manifest);
|
||||
}, [data?.id, activeOrganization?.id, session?.user?.id]);
|
||||
}, [activeOrganization?.id, session?.user?.id]);
|
||||
|
||||
return (
|
||||
<Dialog open={isOpen} onOpenChange={setIsOpen}>
|
||||
@@ -131,11 +132,7 @@ export const AddGithubProvider = () => {
|
||||
Unsure if you already have an app?
|
||||
</a>
|
||||
<Button
|
||||
disabled={
|
||||
(isOrganization && organizationName.length < 1) ||
|
||||
!activeOrganization?.id ||
|
||||
!session?.user?.id
|
||||
}
|
||||
disabled={isOrganization && organizationName.length < 1}
|
||||
type="submit"
|
||||
className="self-end"
|
||||
>
|
||||
|
||||
@@ -55,7 +55,7 @@ export const AddInvitation = () => {
|
||||
api.notification.getEmailProviders.useQuery();
|
||||
const { mutateAsync: sendInvitation } = api.user.sendInvitation.useMutation();
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const { data: activeOrganization } = authClient.useActiveOrganization();
|
||||
const { data: activeOrganization } = api.organization.active.useQuery();
|
||||
|
||||
const form = useForm<AddInvitation>({
|
||||
defaultValues: {
|
||||
|
||||
@@ -557,8 +557,7 @@ function SidebarLogo() {
|
||||
const { mutateAsync: setDefaultOrganization, isPending: isSettingDefault } =
|
||||
api.organization.setDefault.useMutation();
|
||||
const { isMobile } = useSidebar();
|
||||
const { data: activeOrganization } = authClient.useActiveOrganization();
|
||||
const _utils = api.useUtils();
|
||||
const { data: activeOrganization } = api.organization.active.useQuery();
|
||||
|
||||
const { data: invitations, refetch: refetchInvitations } =
|
||||
api.user.getInvitations.useQuery();
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dokploy",
|
||||
"version": "v0.28.3",
|
||||
"version": "v0.28.4",
|
||||
"private": true,
|
||||
"license": "Apache-2.0",
|
||||
"type": "module",
|
||||
|
||||
@@ -69,8 +69,7 @@ export const mountRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
.input(apiCreateMount)
|
||||
.mutation(async ({ input }) => {
|
||||
await createMount(input);
|
||||
return true;
|
||||
return await createMount(input);
|
||||
}),
|
||||
remove: protectedProcedure
|
||||
.input(apiRemoveMount)
|
||||
|
||||
@@ -355,4 +355,13 @@ export const organizationRouter = createTRPCRouter({
|
||||
|
||||
return { success: true };
|
||||
}),
|
||||
active: protectedProcedure.query(async ({ ctx }) => {
|
||||
if (!ctx.session.activeOrganizationId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return await db.query.organization.findFirst({
|
||||
where: eq(organization.id, ctx.session.activeOrganizationId),
|
||||
});
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -2,8 +2,24 @@ import path from "node:path";
|
||||
import Docker from "dockerode";
|
||||
|
||||
export const IS_CLOUD = process.env.IS_CLOUD === "true";
|
||||
export const DOCKER_API_VERSION = process.env.DOCKER_API_VERSION;
|
||||
export const DOCKER_HOST = process.env.DOCKER_HOST;
|
||||
export const DOCKER_PORT = process.env.DOCKER_PORT
|
||||
? Number(process.env.DOCKER_PORT)
|
||||
: undefined;
|
||||
|
||||
export const CLEANUP_CRON_JOB = "50 23 * * *";
|
||||
export const docker = new Docker();
|
||||
export const docker = new Docker({
|
||||
...(DOCKER_API_VERSION && {
|
||||
version: DOCKER_API_VERSION,
|
||||
}),
|
||||
...(DOCKER_HOST && {
|
||||
host: DOCKER_HOST,
|
||||
}),
|
||||
...(DOCKER_PORT && {
|
||||
port: DOCKER_PORT,
|
||||
}),
|
||||
});
|
||||
|
||||
// When not set, use the legacy default so 2FA remains working for users who
|
||||
// enabled it before BETTER_AUTH_SECRET was introduced .
|
||||
|
||||
@@ -365,12 +365,13 @@ const createSchema = createInsertSchema(applications, {
|
||||
previewPath: z.string().optional(),
|
||||
previewCertificateType: z.enum(["letsencrypt", "none", "custom"]).optional(),
|
||||
previewRequireCollaboratorPermissions: z.boolean().optional(),
|
||||
watchPaths: z.array(z.string()).optional(),
|
||||
watchPaths: z.array(z.string()).optional().optional(),
|
||||
previewLabels: z.array(z.string()).optional(),
|
||||
cleanCache: z.boolean().optional(),
|
||||
stopGracePeriodSwarm: z.bigint().nullable(),
|
||||
endpointSpecSwarm: EndpointSpecSwarmSchema.nullable(),
|
||||
ulimitsSwarm: UlimitsSwarmSchema.nullable(),
|
||||
enableSubmodules: z.boolean().optional(),
|
||||
});
|
||||
|
||||
export const apiCreateApplication = createSchema.pick({
|
||||
@@ -433,13 +434,13 @@ export const apiSaveGithubProvider = createSchema
|
||||
owner: true,
|
||||
buildPath: true,
|
||||
githubId: true,
|
||||
watchPaths: true,
|
||||
enableSubmodules: true,
|
||||
})
|
||||
.required()
|
||||
.extend({
|
||||
triggerType: z.enum(["push", "tag"]).default("push"),
|
||||
});
|
||||
})
|
||||
.required()
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveGitlabProvider = createSchema
|
||||
.pick({
|
||||
@@ -451,10 +452,9 @@ export const apiSaveGitlabProvider = createSchema
|
||||
gitlabId: true,
|
||||
gitlabProjectId: true,
|
||||
gitlabPathNamespace: true,
|
||||
watchPaths: true,
|
||||
enableSubmodules: true,
|
||||
})
|
||||
.required();
|
||||
.required()
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveBitbucketProvider = createSchema
|
||||
.pick({
|
||||
@@ -465,10 +465,9 @@ export const apiSaveBitbucketProvider = createSchema
|
||||
bitbucketRepositorySlug: true,
|
||||
bitbucketId: true,
|
||||
applicationId: true,
|
||||
watchPaths: true,
|
||||
enableSubmodules: true,
|
||||
})
|
||||
.required();
|
||||
.required()
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveGiteaProvider = createSchema
|
||||
.pick({
|
||||
@@ -478,10 +477,9 @@ export const apiSaveGiteaProvider = createSchema
|
||||
giteaOwner: true,
|
||||
giteaRepository: true,
|
||||
giteaId: true,
|
||||
watchPaths: true,
|
||||
enableSubmodules: true,
|
||||
})
|
||||
.required();
|
||||
.required()
|
||||
.merge(createSchema.pick({ enableSubmodules: true, watchPaths: true }));
|
||||
|
||||
export const apiSaveDockerProvider = createSchema
|
||||
.pick({
|
||||
@@ -506,6 +504,7 @@ export const apiSaveGitProvider = createSchema
|
||||
.merge(
|
||||
createSchema.pick({
|
||||
customGitSSHKeyId: true,
|
||||
enableSubmodules: true,
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
@@ -99,17 +99,15 @@ const createSchema = createInsertSchema(mounts, {
|
||||
mountPath: z.string().min(1),
|
||||
mountId: z.string().optional(),
|
||||
filePath: z.string().optional(),
|
||||
serviceType: z
|
||||
.enum([
|
||||
"application",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"redis",
|
||||
"compose",
|
||||
])
|
||||
.default("application"),
|
||||
serviceType: z.enum([
|
||||
"application",
|
||||
"postgres",
|
||||
"mysql",
|
||||
"mariadb",
|
||||
"mongo",
|
||||
"redis",
|
||||
"compose",
|
||||
]),
|
||||
});
|
||||
|
||||
export type ServiceType = NonNullable<
|
||||
|
||||
@@ -16,7 +16,7 @@ function shEscape(s: string | undefined): string {
|
||||
return `'${s.replace(/'/g, `'\\''`)}'`;
|
||||
}
|
||||
|
||||
function safeDockerLoginCommand(
|
||||
export function safeDockerLoginCommand(
|
||||
registry: string | undefined,
|
||||
user: string | undefined,
|
||||
pass: string | undefined,
|
||||
|
||||
@@ -23,7 +23,7 @@ import { findDeploymentById } from "./deployment";
|
||||
import type { Mount } from "./mount";
|
||||
import type { Port } from "./port";
|
||||
import type { Project } from "./project";
|
||||
import type { Registry } from "./registry";
|
||||
import { type Registry, safeDockerLoginCommand } from "./registry";
|
||||
|
||||
export const createRollback = async (
|
||||
input: z.infer<typeof createRollbackSchema>,
|
||||
@@ -111,7 +111,7 @@ const deleteRollbackImage = async (image: string, serverId?: string | null) => {
|
||||
const command = `docker image rm ${image} --force`;
|
||||
|
||||
if (serverId) {
|
||||
await execAsyncRemote(command, serverId);
|
||||
await execAsyncRemote(serverId, command);
|
||||
} else {
|
||||
await execAsync(command);
|
||||
}
|
||||
@@ -171,6 +171,23 @@ export const rollback = async (rollbackId: string) => {
|
||||
);
|
||||
};
|
||||
|
||||
const dockerLoginForRegistry = async (
|
||||
registry: Registry,
|
||||
serverId?: string | null,
|
||||
) => {
|
||||
const loginCommand = safeDockerLoginCommand(
|
||||
registry.registryUrl,
|
||||
registry.username,
|
||||
registry.password,
|
||||
);
|
||||
|
||||
if (serverId) {
|
||||
await execAsyncRemote(serverId, loginCommand);
|
||||
} else {
|
||||
await execAsync(loginCommand);
|
||||
}
|
||||
};
|
||||
|
||||
const rollbackApplication = async (
|
||||
appName: string,
|
||||
image: string,
|
||||
@@ -188,6 +205,14 @@ const rollbackApplication = async (
|
||||
throw new Error("Full context is required for rollback");
|
||||
}
|
||||
|
||||
// Ensure Docker daemon is authenticated with the rollback registry
|
||||
// before updating the swarm service. The authconfig in CreateServiceOptions
|
||||
// alone is not sufficient — Docker Swarm also relies on the daemon's
|
||||
// cached credentials (~/.docker/config.json) to distribute auth to nodes.
|
||||
if (fullContext.rollbackRegistry) {
|
||||
await dockerLoginForRegistry(fullContext.rollbackRegistry, serverId);
|
||||
}
|
||||
|
||||
const docker = await getRemoteDocker(serverId);
|
||||
|
||||
// Use the same configuration as mechanizeDockerContainer
|
||||
|
||||
@@ -67,7 +67,7 @@ export const runWebServerBackup = async (backup: BackupSchedule) => {
|
||||
await execAsync(cleanupCommand);
|
||||
|
||||
await execAsync(
|
||||
`rsync -a --ignore-errors ${BASE_PATH}/ ${tempDir}/filesystem/`,
|
||||
`rsync -a --ignore-errors --no-specials --no-devices ${BASE_PATH}/ ${tempDir}/filesystem/`,
|
||||
);
|
||||
|
||||
writeStream.write("Copied filesystem to temp directory\n");
|
||||
|
||||
@@ -152,16 +152,13 @@ export const createRouterConfig = async (
|
||||
}
|
||||
|
||||
if ((entryPoint === "websecure" && https) || !https) {
|
||||
// redirects
|
||||
for (const redirect of redirects) {
|
||||
let middlewareName = `redirect-${appName}-${redirect.uniqueConfigKey}`;
|
||||
if (domain.domainType === "preview") {
|
||||
middlewareName = `redirect-${appName.replace(
|
||||
/^preview-(.+)-[^-]+$/,
|
||||
"$1",
|
||||
)}-${redirect.uniqueConfigKey}`;
|
||||
// redirects - skip for preview deployments as wildcard subdomains
|
||||
// should not inherit parent redirect rules (e.g., www-redirect)
|
||||
if (domain.domainType !== "preview") {
|
||||
for (const redirect of redirects) {
|
||||
const middlewareName = `redirect-${appName}-${redirect.uniqueConfigKey}`;
|
||||
routerConfig.middlewares?.push(middlewareName);
|
||||
}
|
||||
routerConfig.middlewares?.push(middlewareName);
|
||||
}
|
||||
|
||||
// security
|
||||
|
||||
Reference in New Issue
Block a user