mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
xMerge branch 'canary' into feature/delete-docker-volumes
This commit is contained in:
@@ -20,6 +20,8 @@ import {
|
||||
getUserByToken,
|
||||
lucia,
|
||||
luciaToken,
|
||||
removeAdminByAuthId,
|
||||
removeUserByAuthId,
|
||||
sendDiscordNotification,
|
||||
sendEmailNotification,
|
||||
updateAuthById,
|
||||
@@ -59,14 +61,20 @@ export const authRouter = createTRPCRouter({
|
||||
if (IS_CLOUD) {
|
||||
await sendDiscordNotificationWelcome(newAdmin);
|
||||
await sendVerificationEmail(newAdmin.id);
|
||||
return true;
|
||||
return {
|
||||
status: "success",
|
||||
type: "cloud",
|
||||
};
|
||||
}
|
||||
const session = await lucia.createSession(newAdmin.id || "", {});
|
||||
ctx.res.appendHeader(
|
||||
"Set-Cookie",
|
||||
lucia.createSessionCookie(session.id).serialize(),
|
||||
);
|
||||
return true;
|
||||
return {
|
||||
status: "success",
|
||||
type: "selfhosted",
|
||||
};
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
@@ -178,6 +186,20 @@ export const authRouter = createTRPCRouter({
|
||||
update: protectedProcedure
|
||||
.input(apiUpdateAuth)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
const currentAuth = await findAuthByEmail(ctx.user.email);
|
||||
|
||||
if (input.password) {
|
||||
const correctPassword = bcrypt.compareSync(
|
||||
input.password,
|
||||
currentAuth?.password || "",
|
||||
);
|
||||
if (!correctPassword) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Current password is incorrect",
|
||||
});
|
||||
}
|
||||
}
|
||||
const auth = await updateAuthById(ctx.user.authId, {
|
||||
...(input.email && { email: input.email.toLowerCase() }),
|
||||
...(input.password && {
|
||||
@@ -188,6 +210,47 @@ export const authRouter = createTRPCRouter({
|
||||
|
||||
return auth;
|
||||
}),
|
||||
removeSelfAccount: protectedProcedure
|
||||
.input(
|
||||
z.object({
|
||||
password: z.string().min(1),
|
||||
}),
|
||||
)
|
||||
.mutation(async ({ ctx, input }) => {
|
||||
if (!IS_CLOUD) {
|
||||
throw new TRPCError({
|
||||
code: "NOT_FOUND",
|
||||
message: "This feature is only available in the cloud version",
|
||||
});
|
||||
}
|
||||
const currentAuth = await findAuthByEmail(ctx.user.email);
|
||||
|
||||
const correctPassword = bcrypt.compareSync(
|
||||
input.password,
|
||||
currentAuth?.password || "",
|
||||
);
|
||||
|
||||
if (!correctPassword) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: "Password is incorrect",
|
||||
});
|
||||
}
|
||||
const { req, res } = ctx;
|
||||
const { session } = await validateRequest(req, res);
|
||||
if (!session) return false;
|
||||
|
||||
await lucia.invalidateSession(session.id);
|
||||
res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
|
||||
|
||||
if (ctx.user.rol === "admin") {
|
||||
await removeAdminByAuthId(ctx.user.authId);
|
||||
} else {
|
||||
await removeUserByAuthId(ctx.user.authId);
|
||||
}
|
||||
|
||||
return true;
|
||||
}),
|
||||
|
||||
generateToken: protectedProcedure.mutation(async ({ ctx, input }) => {
|
||||
const auth = await findAuthById(ctx.user.authId);
|
||||
@@ -440,7 +503,7 @@ export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
|
||||
webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
|
||||
},
|
||||
{
|
||||
title: "✅ New User Registered",
|
||||
title: " New User Registered",
|
||||
color: 0x00ff00,
|
||||
fields: [
|
||||
{
|
||||
|
||||
@@ -18,6 +18,7 @@ import {
|
||||
import {
|
||||
IS_CLOUD,
|
||||
createServer,
|
||||
defaultCommand,
|
||||
deleteServer,
|
||||
findAdminById,
|
||||
findServerById,
|
||||
@@ -25,6 +26,7 @@ import {
|
||||
getPublicIpWithFallback,
|
||||
haveActiveServices,
|
||||
removeDeploymentsByServerId,
|
||||
serverAudit,
|
||||
serverSetup,
|
||||
serverValidate,
|
||||
updateServerById,
|
||||
@@ -69,6 +71,11 @@ export const serverRouter = createTRPCRouter({
|
||||
|
||||
return server;
|
||||
}),
|
||||
getDefaultCommand: protectedProcedure
|
||||
.input(apiFindOneServer)
|
||||
.query(async ({ input, ctx }) => {
|
||||
return defaultCommand();
|
||||
}),
|
||||
all: protectedProcedure.query(async ({ ctx }) => {
|
||||
const result = await db
|
||||
.select({
|
||||
@@ -160,6 +167,57 @@ export const serverRouter = createTRPCRouter({
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
security: protectedProcedure
|
||||
.input(apiFindOneServer)
|
||||
.query(async ({ input, ctx }) => {
|
||||
try {
|
||||
const server = await findServerById(input.serverId);
|
||||
if (server.adminId !== ctx.user.adminId) {
|
||||
throw new TRPCError({
|
||||
code: "UNAUTHORIZED",
|
||||
message: "You are not authorized to validate this server",
|
||||
});
|
||||
}
|
||||
const response = await serverAudit(input.serverId);
|
||||
return response as unknown as {
|
||||
ufw: {
|
||||
installed: boolean;
|
||||
active: boolean;
|
||||
defaultIncoming: string;
|
||||
};
|
||||
ssh: {
|
||||
enabled: boolean;
|
||||
keyAuth: boolean;
|
||||
permitRootLogin: string;
|
||||
passwordAuth: string;
|
||||
usePam: string;
|
||||
};
|
||||
nonRootUser: {
|
||||
hasValidSudoUser: boolean;
|
||||
};
|
||||
unattendedUpgrades: {
|
||||
installed: boolean;
|
||||
active: boolean;
|
||||
updateEnabled: number;
|
||||
upgradeEnabled: number;
|
||||
};
|
||||
fail2ban: {
|
||||
installed: boolean;
|
||||
enabled: boolean;
|
||||
active: boolean;
|
||||
sshEnabled: string;
|
||||
sshMode: string;
|
||||
};
|
||||
};
|
||||
} catch (error) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
message: error instanceof Error ? error?.message : `Error: ${error}`,
|
||||
cause: error as Error,
|
||||
});
|
||||
}
|
||||
}),
|
||||
remove: protectedProcedure
|
||||
.input(apiRemoveServer)
|
||||
.mutation(async ({ input, ctx }) => {
|
||||
|
||||
@@ -17,7 +17,7 @@ import {
|
||||
updateSSHKeyById,
|
||||
} from "@dokploy/server";
|
||||
import { TRPCError } from "@trpc/server";
|
||||
import { eq } from "drizzle-orm";
|
||||
import { desc, eq } from "drizzle-orm";
|
||||
|
||||
export const sshRouter = createTRPCRouter({
|
||||
create: protectedProcedure
|
||||
@@ -71,6 +71,7 @@ export const sshRouter = createTRPCRouter({
|
||||
all: protectedProcedure.query(async ({ ctx }) => {
|
||||
return await db.query.sshKeys.findMany({
|
||||
...(IS_CLOUD && { where: eq(sshKeys.adminId, ctx.user.adminId) }),
|
||||
orderBy: desc(sshKeys.createdAt),
|
||||
});
|
||||
// TODO: Remove this line when the cloud version is ready
|
||||
}),
|
||||
|
||||
@@ -81,7 +81,7 @@ export const stripeRouter = createTRPCRouter({
|
||||
adminId: admin.adminId,
|
||||
},
|
||||
allow_promotion_codes: true,
|
||||
success_url: `${WEBSITE_URL}/dashboard/settings/billing`,
|
||||
success_url: `${WEBSITE_URL}/dashboard/settings/servers?success=true`,
|
||||
cancel_url: `${WEBSITE_URL}/dashboard/settings/billing`,
|
||||
});
|
||||
|
||||
|
||||
@@ -31,6 +31,8 @@ export const setupDockerContainerLogsWebSocketServer = (
|
||||
const url = new URL(req.url || "", `http://${req.headers.host}`);
|
||||
const containerId = url.searchParams.get("containerId");
|
||||
const tail = url.searchParams.get("tail");
|
||||
const search = url.searchParams.get("search");
|
||||
const since = url.searchParams.get("since");
|
||||
const serverId = url.searchParams.get("serverId");
|
||||
const { user, session } = await validateWebSocketRequest(req);
|
||||
|
||||
@@ -51,9 +53,13 @@ export const setupDockerContainerLogsWebSocketServer = (
|
||||
const client = new Client();
|
||||
client
|
||||
.once("ready", () => {
|
||||
const command = `
|
||||
bash -c "docker container logs --tail ${tail} --follow ${containerId}"
|
||||
`;
|
||||
const baseCommand = `docker container logs --timestamps --tail ${tail} ${
|
||||
since === "all" ? "" : `--since ${since}`
|
||||
} --follow ${containerId}`;
|
||||
const escapedSearch = search ? search.replace(/'/g, "'\\''") : "";
|
||||
const command = search
|
||||
? `${baseCommand} 2>&1 | grep --line-buffered -iF "${escapedSearch}"`
|
||||
: baseCommand;
|
||||
client.exec(command, (err, stream) => {
|
||||
if (err) {
|
||||
console.error("Execution error:", err);
|
||||
@@ -91,21 +97,20 @@ export const setupDockerContainerLogsWebSocketServer = (
|
||||
});
|
||||
} else {
|
||||
const shell = getShell();
|
||||
const ptyProcess = spawn(
|
||||
shell,
|
||||
[
|
||||
"-c",
|
||||
`docker container logs --tail ${tail} --follow ${containerId}`,
|
||||
],
|
||||
{
|
||||
name: "xterm-256color",
|
||||
cwd: process.env.HOME,
|
||||
env: process.env,
|
||||
encoding: "utf8",
|
||||
cols: 80,
|
||||
rows: 30,
|
||||
},
|
||||
);
|
||||
const baseCommand = `docker container logs --timestamps --tail ${tail} ${
|
||||
since === "all" ? "" : `--since ${since}`
|
||||
} --follow ${containerId}`;
|
||||
const command = search
|
||||
? `${baseCommand} 2>&1 | grep -iF '${search}'`
|
||||
: baseCommand;
|
||||
const ptyProcess = spawn(shell, ["-c", command], {
|
||||
name: "xterm-256color",
|
||||
cwd: process.env.HOME,
|
||||
env: process.env,
|
||||
encoding: "utf8",
|
||||
cols: 80,
|
||||
rows: 30,
|
||||
});
|
||||
|
||||
ptyProcess.onData((data) => {
|
||||
ws.send(data);
|
||||
|
||||
@@ -102,21 +102,13 @@ export const setupDockerContainerTerminalWebSocketServer = (
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
} else {
|
||||
const shell = getShell();
|
||||
const ptyProcess = spawn(
|
||||
shell,
|
||||
["-c", `docker exec -it ${containerId} ${activeWay}`],
|
||||
{
|
||||
name: "xterm-256color",
|
||||
cwd: process.env.HOME,
|
||||
env: process.env,
|
||||
encoding: "utf8",
|
||||
cols: 80,
|
||||
rows: 30,
|
||||
},
|
||||
{},
|
||||
);
|
||||
|
||||
ptyProcess.onData((data) => {
|
||||
|
||||
@@ -70,53 +70,44 @@ export const setupTerminalWebSocketServer = (
|
||||
let stderr = "";
|
||||
conn
|
||||
.once("ready", () => {
|
||||
conn.shell(
|
||||
{
|
||||
term: "terminal",
|
||||
cols: 80,
|
||||
rows: 30,
|
||||
height: 30,
|
||||
width: 80,
|
||||
},
|
||||
(err, stream) => {
|
||||
if (err) throw err;
|
||||
conn.shell({}, (err, stream) => {
|
||||
if (err) throw err;
|
||||
|
||||
stream
|
||||
.on("close", (code: number, signal: string) => {
|
||||
ws.send(`\nContainer closed with code: ${code}\n`);
|
||||
conn.end();
|
||||
})
|
||||
.on("data", (data: string) => {
|
||||
stdout += data.toString();
|
||||
ws.send(data.toString());
|
||||
})
|
||||
.stderr.on("data", (data) => {
|
||||
stderr += data.toString();
|
||||
ws.send(data.toString());
|
||||
console.error("Error: ", data.toString());
|
||||
});
|
||||
stream
|
||||
.on("close", (code: number, signal: string) => {
|
||||
ws.send(`\nContainer closed with code: ${code}\n`);
|
||||
conn.end();
|
||||
})
|
||||
.on("data", (data: string) => {
|
||||
stdout += data.toString();
|
||||
ws.send(data.toString());
|
||||
})
|
||||
.stderr.on("data", (data) => {
|
||||
stderr += data.toString();
|
||||
ws.send(data.toString());
|
||||
console.error("Error: ", data.toString());
|
||||
});
|
||||
|
||||
ws.on("message", (message) => {
|
||||
try {
|
||||
let command: string | Buffer[] | Buffer | ArrayBuffer;
|
||||
if (Buffer.isBuffer(message)) {
|
||||
command = message.toString("utf8");
|
||||
} else {
|
||||
command = message;
|
||||
}
|
||||
stream.write(command.toString());
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
const errorMessage = error?.message as unknown as string;
|
||||
ws.send(errorMessage);
|
||||
ws.on("message", (message) => {
|
||||
try {
|
||||
let command: string | Buffer[] | Buffer | ArrayBuffer;
|
||||
if (Buffer.isBuffer(message)) {
|
||||
command = message.toString("utf8");
|
||||
} else {
|
||||
command = message;
|
||||
}
|
||||
});
|
||||
stream.write(command.toString());
|
||||
} catch (error) {
|
||||
// @ts-ignore
|
||||
const errorMessage = error?.message as unknown as string;
|
||||
ws.send(errorMessage);
|
||||
}
|
||||
});
|
||||
|
||||
ws.on("close", () => {
|
||||
stream.end();
|
||||
});
|
||||
},
|
||||
);
|
||||
ws.on("close", () => {
|
||||
stream.end();
|
||||
});
|
||||
});
|
||||
})
|
||||
.on("error", (err) => {
|
||||
if (err.level === "client-authentication") {
|
||||
@@ -133,7 +124,6 @@ export const setupTerminalWebSocketServer = (
|
||||
port: server.port,
|
||||
username: server.username,
|
||||
privateKey: server.sshKey?.privateKey,
|
||||
timeout: 99999,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user