@@ -155,7 +149,7 @@ export default function Home() {
Home.getLayout = (page: ReactElement) => {
return
{page} ;
};
-export async function getServerSideProps(context: GetServerSidePropsContext) {
+export async function getServerSideProps(_context: GetServerSidePropsContext) {
if (!IS_CLOUD) {
return {
redirect: {
diff --git a/apps/dokploy/pages/swagger.tsx b/apps/dokploy/pages/swagger.tsx
index b5deb2ac8..11ea0731d 100644
--- a/apps/dokploy/pages/swagger.tsx
+++ b/apps/dokploy/pages/swagger.tsx
@@ -30,7 +30,41 @@ const Home: NextPage = () => {
return (
-
+ (args: any) => {
+ const result = ori(args);
+ const apiKey = args?.apiKey?.value;
+ if (apiKey) {
+ localStorage.setItem("swagger_api_key", apiKey);
+ }
+ return result;
+ },
+ logout: (ori: any) => (args: any) => {
+ const result = ori(args);
+ localStorage.removeItem("swagger_api_key");
+ return result;
+ },
+ },
+ },
+ },
+ },
+ ]}
+ requestInterceptor={(request: any) => {
+ const apiKey = localStorage.getItem("swagger_api_key");
+ if (apiKey) {
+ request.headers = request.headers || {};
+ request.headers["x-api-key"] = apiKey;
+ }
+ return request;
+ }}
+ />
);
};
@@ -38,7 +72,7 @@ const Home: NextPage = () => {
export default Home;
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req, res } = context;
- const { user, session } = await validateRequest(context.req, context.res);
+ const { user, session } = await validateRequest(context.req);
if (!user) {
return {
redirect: {
@@ -53,17 +87,17 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
req: req as any,
res: res as any,
db: null as any,
- session: session,
- user: user,
+ session: session as any,
+ user: user as any,
},
transformer: superjson,
});
- if (user.rol === "user") {
- const result = await helpers.user.byAuthId.fetch({
- authId: user.id,
+ if (user.role === "member") {
+ const userR = await helpers.user.one.fetch({
+ userId: user.id,
});
- if (!result.canAccessToAPI) {
+ if (!userR?.canAccessToAPI) {
return {
redirect: {
permanent: true,
diff --git a/apps/dokploy/public/locales/ml/common.json b/apps/dokploy/public/locales/ml/common.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/apps/dokploy/public/locales/ml/common.json
@@ -0,0 +1 @@
+{}
diff --git a/apps/dokploy/public/locales/ml/settings.json b/apps/dokploy/public/locales/ml/settings.json
new file mode 100644
index 000000000..cb62b6ec3
--- /dev/null
+++ b/apps/dokploy/public/locales/ml/settings.json
@@ -0,0 +1,58 @@
+{
+ "settings.common.save": "സേവ് ചെയ്യുക",
+ "settings.common.enterTerminal": "ടർമിനലിൽ പ്രവേശിക്കുക",
+ "settings.server.domain.title": "സർവർ ഡോമെയ്ൻ",
+ "settings.server.domain.description": "നിങ്ങളുടെ സർവർ അപ്ലിക്കേഷനിൽ ഒരു ഡോമെയ്ൻ ചേർക്കുക.",
+ "settings.server.domain.form.domain": "ഡോമെയ്ൻ",
+ "settings.server.domain.form.letsEncryptEmail": "ലെറ്റ്സ് എൻക്രിപ്റ്റ് ഇമെയിൽ",
+ "settings.server.domain.form.certificate.label": "സർട്ടിഫിക്കറ്റ് പ്രൊവൈഡർ",
+ "settings.server.domain.form.certificate.placeholder": "ഒരു സർട്ടിഫിക്കറ്റ് തിരഞ്ഞെടുക്കുക",
+ "settings.server.domain.form.certificateOptions.none": "ഒന്നുമില്ല",
+ "settings.server.domain.form.certificateOptions.letsencrypt": "ലെറ്റ്സ് എൻക്രിപ്റ്റ്",
+
+ "settings.server.webServer.title": "വെബ് സർവർ",
+ "settings.server.webServer.description": "വെബ് സർവർ റീലോഡ് ചെയ്യുക അല്ലെങ്കിൽ ശുചീകരിക്കുക.",
+ "settings.server.webServer.actions": "നടപടികൾ",
+ "settings.server.webServer.reload": "റീലോഡ് ചെയ്യുക",
+ "settings.server.webServer.watchLogs": "ലോഗുകൾ കാണുക",
+ "settings.server.webServer.updateServerIp": "സർവർ IP അപ്ഡേറ്റ് ചെയ്യുക",
+ "settings.server.webServer.server.label": "സർവർ",
+ "settings.server.webServer.traefik.label": "ട്രാഫിക്",
+ "settings.server.webServer.traefik.modifyEnv": "ചുറ്റുപാടുകൾ മാറ്റുക",
+ "settings.server.webServer.traefik.managePorts": "അധിക പോർട്ട് മാപ്പിംഗ്",
+ "settings.server.webServer.traefik.managePortsDescription": "ട്രാഫിക്കിനായി അധിക പോർട്ടുകൾ ചേർക്കുക അല്ലെങ്കിൽ നീക്കം ചെയ്യുക",
+ "settings.server.webServer.traefik.targetPort": "ടാർഗറ്റ് പോർട്ട്",
+ "settings.server.webServer.traefik.publishedPort": "പ്രസിദ്ധീകരിച്ച പോർട്ട്",
+ "settings.server.webServer.traefik.addPort": "പോർട്ട് ചേർക്കുക",
+ "settings.server.webServer.traefik.portsUpdated": "പോർട്ടുകൾ വിജയകരമായി അപ്ഡേറ്റ് ചെയ്തു",
+ "settings.server.webServer.traefik.portsUpdateError": "പോർട്ടുകൾ അപ്ഡേറ്റ് ചെയ്യാൻ പരാജയപ്പെട്ടു",
+ "settings.server.webServer.traefik.publishMode": "പ്രസിദ്ധീകരണ മോഡ്",
+ "settings.server.webServer.storage.label": "ഇടം",
+ "settings.server.webServer.storage.cleanUnusedImages": "ഉപയോഗിക്കാത്ത ഇമേജുകൾ ശുചീകരിക്കുക",
+ "settings.server.webServer.storage.cleanUnusedVolumes": "ഉപയോഗിക്കാത്ത വോള്യങ്ങൾ ശുചീകരിക്കുക",
+ "settings.server.webServer.storage.cleanStoppedContainers": "നിർത്തിയ കണ്ടെയ്നറുകൾ ശുചീകരിക്കുക",
+ "settings.server.webServer.storage.cleanDockerBuilder": "ഡോക്കർ ബിൽഡറും സിസ്റ്റവും ശുചീകരിക്കുക",
+ "settings.server.webServer.storage.cleanMonitoring": "മോണിറ്ററിംഗ് ശുചീകരിക്കുക",
+ "settings.server.webServer.storage.cleanAll": "എല്ലാം ശുചീകരിക്കുക",
+
+ "settings.profile.title": "അക്കൗണ്ട്",
+ "settings.profile.description": "നിങ്ങളുടെ പ്രൊഫൈൽ വിശദാംശങ്ങൾ ഇവിടെ മാറ്റുക.",
+ "settings.profile.email": "ഇമെയിൽ",
+ "settings.profile.password": "പാസ്വേഡ്",
+ "settings.profile.avatar": "അവതാർ",
+
+ "settings.appearance.title": "ദൃശ്യമാനം",
+ "settings.appearance.description": "നിങ്ങളുടെ ഡാഷ്ബോർഡിന്റെ തീം ഇഷ്ടാനുസൃതമാക്കുക.",
+ "settings.appearance.theme": "തീം",
+ "settings.appearance.themeDescription": "നിങ്ങളുടെ ഡാഷ്ബോർഡിന് ഒരു തീം തിരഞ്ഞെടുക്കുക",
+ "settings.appearance.themes.light": "ലൈറ്റ്",
+ "settings.appearance.themes.dark": "ഡാർക്ക്",
+ "settings.appearance.themes.system": "സിസ്റ്റം",
+ "settings.appearance.language": "ഭാഷ",
+ "settings.appearance.languageDescription": "നിങ്ങളുടെ ഡാഷ്ബോർഡിന് ഒരു ഭാഷ തിരഞ്ഞെടുക്കുക",
+
+ "settings.terminal.connectionSettings": "കണക്ഷൻ ക്രമീകരണങ്ങൾ",
+ "settings.terminal.ipAddress": "IP വിലാസം",
+ "settings.terminal.port": "പോർട്ട്",
+ "settings.terminal.username": "ഉപയോക്തൃനാമം"
+}
diff --git a/apps/dokploy/public/locales/ru/settings.json b/apps/dokploy/public/locales/ru/settings.json
index 1e71d7102..0d87ed159 100644
--- a/apps/dokploy/public/locales/ru/settings.json
+++ b/apps/dokploy/public/locales/ru/settings.json
@@ -1,5 +1,6 @@
{
"settings.common.save": "Сохранить",
+ "settings.common.enterTerminal": "Открыть терминал",
"settings.server.domain.title": "Домен сервера",
"settings.server.domain.description": "Установите домен для вашего серверного приложения Dokploy.",
"settings.server.domain.form.domain": "Домен",
@@ -7,18 +8,26 @@
"settings.server.domain.form.certificate.label": "Сертификат",
"settings.server.domain.form.certificate.placeholder": "Выберите сертификат",
"settings.server.domain.form.certificateOptions.none": "Нет",
- "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (По умолчанию)",
+ "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt",
"settings.server.webServer.title": "Веб-сервер",
"settings.server.webServer.description": "Перезагрузка или очистка веб-сервера.",
- "settings.server.webServer.server.label": "Сервер",
- "settings.server.webServer.traefik.label": "Traefik",
- "settings.server.webServer.storage.label": "Дисковое пространство",
"settings.server.webServer.actions": "Действия",
"settings.server.webServer.reload": "Перезагрузить",
"settings.server.webServer.watchLogs": "Просмотр логов",
"settings.server.webServer.updateServerIp": "Изменить IP адрес",
+ "settings.server.webServer.server.label": "Сервер",
+ "settings.server.webServer.traefik.label": "Traefik",
"settings.server.webServer.traefik.modifyEnv": "Изменить переменные окружения",
+ "settings.server.webServer.traefik.managePorts": "Назначение портов",
+ "settings.server.webServer.traefik.managePortsDescription": "Добавить или удалить дополнительные порты для Traefik",
+ "settings.server.webServer.traefik.targetPort": "Внутренний порт",
+ "settings.server.webServer.traefik.publishedPort": "Внешний порт",
+ "settings.server.webServer.traefik.addPort": "Добавить порт",
+ "settings.server.webServer.traefik.portsUpdated": "Порты успешно обновлены",
+ "settings.server.webServer.traefik.portsUpdateError": "Не удалось обновить порты",
+ "settings.server.webServer.traefik.publishMode": "Режим сопоставления",
+ "settings.server.webServer.storage.label": "Дисковое пространство",
"settings.server.webServer.storage.cleanUnusedImages": "Очистить неиспользуемые образы",
"settings.server.webServer.storage.cleanUnusedVolumes": "Очистить неиспользуемые тома",
"settings.server.webServer.storage.cleanStoppedContainers": "Очистить остановленные контейнеры",
@@ -40,5 +49,10 @@
"settings.appearance.themes.dark": "Темная",
"settings.appearance.themes.system": "Системная",
"settings.appearance.language": "Язык",
- "settings.appearance.languageDescription": "Select a language for your dashboard"
+ "settings.appearance.languageDescription": "Выберите язык для панели управления",
+
+ "settings.terminal.connectionSettings": "Настройки подключения",
+ "settings.terminal.ipAddress": "IP адрес",
+ "settings.terminal.port": "Порт",
+ "settings.terminal.username": "Имя пользователя"
}
diff --git a/apps/dokploy/public/locales/uk/common.json b/apps/dokploy/public/locales/uk/common.json
new file mode 100644
index 000000000..0967ef424
--- /dev/null
+++ b/apps/dokploy/public/locales/uk/common.json
@@ -0,0 +1 @@
+{}
diff --git a/apps/dokploy/public/locales/uk/settings.json b/apps/dokploy/public/locales/uk/settings.json
new file mode 100644
index 000000000..766a1bff9
--- /dev/null
+++ b/apps/dokploy/public/locales/uk/settings.json
@@ -0,0 +1,58 @@
+{
+ "settings.common.save": "Зберегти",
+ "settings.common.enterTerminal": "Увійти в термінал",
+ "settings.server.domain.title": "Домен сервера",
+ "settings.server.domain.description": "Додайте домен до вашого серверного застосунку.",
+ "settings.server.domain.form.domain": "Домен",
+ "settings.server.domain.form.letsEncryptEmail": "Електронна пошта для Let's Encrypt",
+ "settings.server.domain.form.certificate.label": "Постачальник сертифікатів",
+ "settings.server.domain.form.certificate.placeholder": "Оберіть сертифікат",
+ "settings.server.domain.form.certificateOptions.none": "Відсутній",
+ "settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt",
+
+ "settings.server.webServer.title": "Веб-сервер",
+ "settings.server.webServer.description": "Перезавантажте або очистьте веб-сервер.",
+ "settings.server.webServer.actions": "Дії",
+ "settings.server.webServer.reload": "Перезавантажити",
+ "settings.server.webServer.watchLogs": "Перегляд логів",
+ "settings.server.webServer.updateServerIp": "Оновити IP-адресу сервера",
+ "settings.server.webServer.server.label": "Сервер",
+ "settings.server.webServer.traefik.label": "Traefik",
+ "settings.server.webServer.traefik.modifyEnv": "Змінити середовище",
+ "settings.server.webServer.traefik.managePorts": "Додаткові порти",
+ "settings.server.webServer.traefik.managePortsDescription": "Додайте або видаліть порти для Traefik",
+ "settings.server.webServer.traefik.targetPort": "Цільовий порт",
+ "settings.server.webServer.traefik.publishedPort": "Опублікований порт",
+ "settings.server.webServer.traefik.addPort": "Додати порт",
+ "settings.server.webServer.traefik.portsUpdated": "Порти успішно оновлено",
+ "settings.server.webServer.traefik.portsUpdateError": "Не вдалося оновити порти",
+ "settings.server.webServer.traefik.publishMode": "Режим публікації",
+ "settings.server.webServer.storage.label": "Дисковий простір",
+ "settings.server.webServer.storage.cleanUnusedImages": "Очистити невикористані образи",
+ "settings.server.webServer.storage.cleanUnusedVolumes": "Очистити невикористані томи",
+ "settings.server.webServer.storage.cleanStoppedContainers": "Очистити зупинені контейнери",
+ "settings.server.webServer.storage.cleanDockerBuilder": "Очистити Docker Builder і систему",
+ "settings.server.webServer.storage.cleanMonitoring": "Очистити моніторинг",
+ "settings.server.webServer.storage.cleanAll": "Очистити все",
+
+ "settings.profile.title": "Обліковий запис",
+ "settings.profile.description": "Змініть дані вашого профілю.",
+ "settings.profile.email": "Електронна пошта",
+ "settings.profile.password": "Пароль",
+ "settings.profile.avatar": "Аватар",
+
+ "settings.appearance.title": "Зовнішній вигляд",
+ "settings.appearance.description": "Налаштуйте тему вашої панелі керування.",
+ "settings.appearance.theme": "Тема",
+ "settings.appearance.themeDescription": "Оберіть тему для вашої панелі керування",
+ "settings.appearance.themes.light": "Світла",
+ "settings.appearance.themes.dark": "Темна",
+ "settings.appearance.themes.system": "Системна",
+ "settings.appearance.language": "Мова",
+ "settings.appearance.languageDescription": "Оберіть мову для вашої панелі керування",
+
+ "settings.terminal.connectionSettings": "Налаштування з'єднання",
+ "settings.terminal.ipAddress": "IP-адреса",
+ "settings.terminal.port": "Порт",
+ "settings.terminal.username": "Ім'я користувача"
+}
diff --git a/apps/dokploy/public/templates/alist.svg b/apps/dokploy/public/templates/alist.svg
new file mode 100644
index 000000000..37d5fdcd9
--- /dev/null
+++ b/apps/dokploy/public/templates/alist.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/answer.png b/apps/dokploy/public/templates/answer.png
new file mode 100644
index 000000000..3fca604d4
Binary files /dev/null and b/apps/dokploy/public/templates/answer.png differ
diff --git a/apps/dokploy/public/templates/appwrite.svg b/apps/dokploy/public/templates/appwrite.svg
new file mode 100644
index 000000000..2034a812a
--- /dev/null
+++ b/apps/dokploy/public/templates/appwrite.svg
@@ -0,0 +1,9 @@
+
+
+
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/convex.svg b/apps/dokploy/public/templates/convex.svg
new file mode 100644
index 000000000..8622c4c07
--- /dev/null
+++ b/apps/dokploy/public/templates/convex.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/apps/dokploy/public/templates/erpnext.svg b/apps/dokploy/public/templates/erpnext.svg
new file mode 100644
index 000000000..d699ea2ad
--- /dev/null
+++ b/apps/dokploy/public/templates/erpnext.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/apps/dokploy/public/templates/evolutionapi.png b/apps/dokploy/public/templates/evolutionapi.png
new file mode 100644
index 000000000..bd9b3850a
Binary files /dev/null and b/apps/dokploy/public/templates/evolutionapi.png differ
diff --git a/apps/dokploy/public/templates/formbricks.png b/apps/dokploy/public/templates/formbricks.png
new file mode 100644
index 000000000..2bf1ca1fb
Binary files /dev/null and b/apps/dokploy/public/templates/formbricks.png differ
diff --git a/apps/dokploy/public/templates/frappe-hr.svg b/apps/dokploy/public/templates/frappe-hr.svg
new file mode 100644
index 000000000..4cbf51641
--- /dev/null
+++ b/apps/dokploy/public/templates/frappe-hr.svg
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/apps/dokploy/public/templates/glance.png b/apps/dokploy/public/templates/glance.png
new file mode 100644
index 000000000..54fc41311
Binary files /dev/null and b/apps/dokploy/public/templates/glance.png differ
diff --git a/apps/dokploy/public/templates/homarr.png b/apps/dokploy/public/templates/homarr.png
new file mode 100644
index 000000000..25581ea5c
Binary files /dev/null and b/apps/dokploy/public/templates/homarr.png differ
diff --git a/apps/dokploy/public/templates/linkwarden.png b/apps/dokploy/public/templates/linkwarden.png
new file mode 100644
index 000000000..843f681eb
Binary files /dev/null and b/apps/dokploy/public/templates/linkwarden.png differ
diff --git a/apps/dokploy/public/templates/mailpit.svg b/apps/dokploy/public/templates/mailpit.svg
new file mode 100644
index 000000000..58675a267
--- /dev/null
+++ b/apps/dokploy/public/templates/mailpit.svg
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/maybe.svg b/apps/dokploy/public/templates/maybe.svg
new file mode 100644
index 000000000..a4a877360
--- /dev/null
+++ b/apps/dokploy/public/templates/maybe.svg
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/outline.png b/apps/dokploy/public/templates/outline.png
new file mode 100644
index 000000000..b241f01d7
Binary files /dev/null and b/apps/dokploy/public/templates/outline.png differ
diff --git a/apps/dokploy/public/templates/pocket-id.svg b/apps/dokploy/public/templates/pocket-id.svg
new file mode 100644
index 000000000..0ee89b14b
--- /dev/null
+++ b/apps/dokploy/public/templates/pocket-id.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/registry.png b/apps/dokploy/public/templates/registry.png
new file mode 100644
index 000000000..39418022e
Binary files /dev/null and b/apps/dokploy/public/templates/registry.png differ
diff --git a/apps/dokploy/public/templates/shlink.svg b/apps/dokploy/public/templates/shlink.svg
new file mode 100644
index 000000000..6253cd361
--- /dev/null
+++ b/apps/dokploy/public/templates/shlink.svg
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/dokploy/public/templates/spacedrive.png b/apps/dokploy/public/templates/spacedrive.png
new file mode 100644
index 000000000..a10fffd52
Binary files /dev/null and b/apps/dokploy/public/templates/spacedrive.png differ
diff --git a/apps/dokploy/public/templates/superset.svg b/apps/dokploy/public/templates/superset.svg
new file mode 100644
index 000000000..522c3b28a
--- /dev/null
+++ b/apps/dokploy/public/templates/superset.svg
@@ -0,0 +1,9 @@
+
+
+ Superset
+
+
+
+
+
+
diff --git a/apps/dokploy/public/templates/trilium.png b/apps/dokploy/public/templates/trilium.png
new file mode 100644
index 000000000..f6afe82f8
Binary files /dev/null and b/apps/dokploy/public/templates/trilium.png differ
diff --git a/apps/dokploy/public/templates/twenty.svg b/apps/dokploy/public/templates/twenty.svg
index cf5223b96..bad18fab3 100644
--- a/apps/dokploy/public/templates/twenty.svg
+++ b/apps/dokploy/public/templates/twenty.svg
@@ -1,6 +1,12 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
diff --git a/apps/dokploy/public/templates/wikijs.svg b/apps/dokploy/public/templates/wikijs.svg
new file mode 100644
index 000000000..78073b234
--- /dev/null
+++ b/apps/dokploy/public/templates/wikijs.svg
@@ -0,0 +1,119 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/apps/dokploy/reset-password.ts b/apps/dokploy/reset-password.ts
index 43b11fdf6..32cab4334 100644
--- a/apps/dokploy/reset-password.ts
+++ b/apps/dokploy/reset-password.ts
@@ -1,6 +1,8 @@
import { findAdmin } from "@dokploy/server";
-import { updateAuthById } from "@dokploy/server";
import { generateRandomPassword } from "@dokploy/server";
+import { db } from "@dokploy/server/db";
+import { account } from "@dokploy/server/db/schema";
+import { eq } from "drizzle-orm";
(async () => {
try {
@@ -8,9 +10,12 @@ import { generateRandomPassword } from "@dokploy/server";
const result = await findAdmin();
- const update = await updateAuthById(result.authId, {
- password: randomPassword.hashedPassword,
- });
+ const update = await db
+ .update(account)
+ .set({
+ password: randomPassword.hashedPassword,
+ })
+ .where(eq(account.userId, result.userId));
if (update) {
console.log("Password reset successful");
diff --git a/apps/dokploy/server/api/root.ts b/apps/dokploy/server/api/root.ts
index 6119ee859..8120b3199 100644
--- a/apps/dokploy/server/api/root.ts
+++ b/apps/dokploy/server/api/root.ts
@@ -20,6 +20,7 @@ import { mongoRouter } from "./routers/mongo";
import { mountRouter } from "./routers/mount";
import { mysqlRouter } from "./routers/mysql";
import { notificationRouter } from "./routers/notification";
+import { organizationRouter } from "./routers/organization";
import { portRouter } from "./routers/port";
import { postgresRouter } from "./routers/postgres";
import { previewDeploymentRouter } from "./routers/preview-deployment";
@@ -34,7 +35,6 @@ import { sshRouter } from "./routers/ssh-key";
import { stripeRouter } from "./routers/stripe";
import { swarmRouter } from "./routers/swarm";
import { userRouter } from "./routers/user";
-
/**
* This is the primary router for your server.
*
@@ -77,6 +77,7 @@ export const appRouter = createTRPCRouter({
stripe: stripeRouter,
swarm: swarmRouter,
ai: aiRouter,
+ organization: organizationRouter,
});
// export type definition of API
diff --git a/apps/dokploy/server/api/routers/admin.ts b/apps/dokploy/server/api/routers/admin.ts
index 696f3c796..47bd9cd9c 100644
--- a/apps/dokploy/server/api/routers/admin.ts
+++ b/apps/dokploy/server/api/routers/admin.ts
@@ -1,103 +1,59 @@
-import { db } from "@/server/db";
+import { apiUpdateWebServerMonitoring } from "@/server/db/schema";
import {
- apiAssignPermissions,
- apiCreateUserInvitation,
- apiFindOneToken,
- apiRemoveUser,
- apiUpdateAdmin,
- users,
-} from "@/server/db/schema";
-import {
- createInvitation,
- findAdminById,
- findUserByAuthId,
+ IS_CLOUD,
findUserById,
- getUserByToken,
- removeUserByAuthId,
- updateAdmin,
+ setupWebMonitoring,
+ updateUser,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
-import { eq } from "drizzle-orm";
-import { adminProcedure, createTRPCRouter, publicProcedure } from "../trpc";
+import { adminProcedure, createTRPCRouter } from "../trpc";
export const adminRouter = createTRPCRouter({
- one: adminProcedure.query(async ({ ctx }) => {
- const { sshPrivateKey, ...rest } = await findAdminById(ctx.user.adminId);
- return {
- haveSSH: !!sshPrivateKey,
- ...rest,
- };
- }),
- update: adminProcedure
- .input(apiUpdateAdmin)
- .mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not allowed to update this admin",
- });
- }
- const { authId } = await findAdminById(ctx.user.adminId);
- return updateAdmin(authId, input);
- }),
- createUserInvitation: adminProcedure
- .input(apiCreateUserInvitation)
+ setupMonitoring: adminProcedure
+ .input(apiUpdateWebServerMonitoring)
.mutation(async ({ input, ctx }) => {
try {
- await createInvitation(input, ctx.user.adminId);
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message:
- "Error creating this user\ncheck if the email is not registered",
- cause: error,
- });
- }
- }),
- removeUser: adminProcedure
- .input(apiRemoveUser)
- .mutation(async ({ input, ctx }) => {
- try {
- const user = await findUserByAuthId(input.authId);
-
- if (user.adminId !== ctx.user.adminId) {
+ if (IS_CLOUD) {
throw new TRPCError({
code: "UNAUTHORIZED",
- message: "You are not allowed to delete this user",
+ message: "Feature disabled on cloud",
});
}
- return await removeUserByAuthId(input.authId);
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error deleting this user",
- cause: error,
- });
- }
- }),
- getUserByToken: publicProcedure
- .input(apiFindOneToken)
- .query(async ({ input }) => {
- return await getUserByToken(input.token);
- }),
- assignPermissions: adminProcedure
- .input(apiAssignPermissions)
- .mutation(async ({ input, ctx }) => {
- try {
- const user = await findUserById(input.userId);
-
- if (user.adminId !== ctx.user.adminId) {
+ const user = await findUserById(ctx.user.ownerId);
+ if (user.id !== ctx.user.ownerId) {
throw new TRPCError({
code: "UNAUTHORIZED",
- message: "You are not allowed to assign permissions",
+ message: "You are not authorized to setup the monitoring",
});
}
- await db
- .update(users)
- .set({
- ...input,
- })
- .where(eq(users.userId, input.userId));
+
+ await updateUser(user.id, {
+ metricsConfig: {
+ server: {
+ type: "Dokploy",
+ refreshRate: input.metricsConfig.server.refreshRate,
+ port: input.metricsConfig.server.port,
+ token: input.metricsConfig.server.token,
+ cronJob: input.metricsConfig.server.cronJob,
+ urlCallback: input.metricsConfig.server.urlCallback,
+ retentionDays: input.metricsConfig.server.retentionDays,
+ thresholds: {
+ cpu: input.metricsConfig.server.thresholds.cpu,
+ memory: input.metricsConfig.server.thresholds.memory,
+ },
+ },
+ containers: {
+ refreshRate: input.metricsConfig.containers.refreshRate,
+ services: {
+ include: input.metricsConfig.containers.services.include || [],
+ exclude: input.metricsConfig.containers.services.exclude || [],
+ },
+ },
+ },
+ });
+
+ const currentServer = await setupWebMonitoring(user.id);
+ return currentServer;
} catch (error) {
throw error;
}
diff --git a/apps/dokploy/server/api/routers/application.ts b/apps/dokploy/server/api/routers/application.ts
index 13b7c80df..e1629b4cb 100644
--- a/apps/dokploy/server/api/routers/application.ts
+++ b/apps/dokploy/server/api/routers/application.ts
@@ -60,8 +60,13 @@ export const applicationRouter = createTRPCRouter({
.input(apiCreateApplication)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -72,7 +77,7 @@ export const applicationRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
@@ -80,8 +85,12 @@ export const applicationRouter = createTRPCRouter({
}
const newApplication = await createApplication(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newApplication.applicationId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newApplication.applicationId,
+ project.organizationId,
+ );
}
return newApplication;
} catch (error: unknown) {
@@ -98,15 +107,18 @@ export const applicationRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneApplication)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
+ if (ctx.user.rol === "member") {
await checkServiceAccess(
- ctx.user.authId,
+ ctx.user.id,
input.applicationId,
+ ctx.session.activeOrganizationId,
"access",
);
}
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -119,7 +131,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiReloadApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this application",
@@ -144,16 +158,19 @@ export const applicationRouter = createTRPCRouter({
delete: protectedProcedure
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
+ if (ctx.user.rol === "member") {
await checkServiceAccess(
- ctx.user.authId,
+ ctx.user.id,
input.applicationId,
+ ctx.session.activeOrganizationId,
"delete",
);
}
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this application",
@@ -184,7 +201,7 @@ export const applicationRouter = createTRPCRouter({
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return result[0];
@@ -194,7 +211,7 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const service = await findApplicationById(input.applicationId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this application",
@@ -214,7 +231,7 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const service = await findApplicationById(input.applicationId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this application",
@@ -235,7 +252,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to redeploy this application",
@@ -268,7 +287,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariables)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -284,7 +305,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveBuildType)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this build type",
@@ -305,7 +328,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveGithubProvider)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this github provider",
@@ -327,7 +352,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveGitlabProvider)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this gitlab provider",
@@ -351,7 +378,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveBitbucketProvider)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this bitbucket provider",
@@ -373,7 +402,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveDockerProvider)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this docker provider",
@@ -394,7 +425,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiSaveGitProvider)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this git provider",
@@ -415,7 +448,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to mark this application as running",
@@ -427,7 +462,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiUpdateApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this application",
@@ -451,7 +488,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to refresh this application",
@@ -466,7 +505,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this application",
@@ -500,7 +541,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to clean this application",
@@ -513,7 +556,9 @@ export const applicationRouter = createTRPCRouter({
.input(apiFindOneApplication)
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to read this application",
@@ -548,7 +593,7 @@ export const applicationRouter = createTRPCRouter({
const app = await findApplicationById(input.applicationId as string);
- if (app.project.adminId !== ctx.user.adminId) {
+ if (app.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this application",
@@ -590,7 +635,9 @@ export const applicationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this application",
@@ -610,7 +657,7 @@ export const applicationRouter = createTRPCRouter({
}),
readAppMonitoring: protectedProcedure
.input(apiFindMonitoringStats)
- .query(async ({ input, ctx }) => {
+ .query(async ({ input }) => {
if (IS_CLOUD) {
throw new TRPCError({
code: "UNAUTHORIZED",
diff --git a/apps/dokploy/server/api/routers/auth.ts b/apps/dokploy/server/api/routers/auth.ts
index f8bbfa9b7..2c7469c3f 100644
--- a/apps/dokploy/server/api/routers/auth.ts
+++ b/apps/dokploy/server/api/routers/auth.ts
@@ -1,523 +1,326 @@
-import {
- apiCreateAdmin,
- apiCreateUser,
- apiFindOneAuth,
- apiLogin,
- apiUpdateAuth,
- apiVerify2FA,
- apiVerifyLogin2FA,
- auth,
-} from "@/server/db/schema";
-import { WEBSITE_URL } from "@/server/utils/stripe";
-import {
- type Auth,
- IS_CLOUD,
- createAdmin,
- createUser,
- findAuthByEmail,
- findAuthById,
- generate2FASecret,
- getUserByToken,
- lucia,
- luciaToken,
- removeAdminByAuthId,
- removeUserByAuthId,
- sendDiscordNotification,
- sendEmailNotification,
- updateAuthById,
- validateRequest,
- verify2FA,
-} from "@dokploy/server";
-import { TRPCError } from "@trpc/server";
-import * as bcrypt from "bcrypt";
-import { isBefore } from "date-fns";
-import { eq } from "drizzle-orm";
-import { nanoid } from "nanoid";
-import { z } from "zod";
-import { db } from "../../db";
-import {
- adminProcedure,
- createTRPCRouter,
- protectedProcedure,
- publicProcedure,
-} from "../trpc";
+import { createTRPCRouter } from "../trpc";
export const authRouter = createTRPCRouter({
- createAdmin: publicProcedure
- .input(apiCreateAdmin)
- .mutation(async ({ ctx, input }) => {
- try {
- if (!IS_CLOUD) {
- const admin = await db.query.admins.findFirst({});
- if (admin) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Admin already exists",
- });
- }
- }
- const newAdmin = await createAdmin(input);
-
- if (IS_CLOUD) {
- await sendDiscordNotificationWelcome(newAdmin);
- await sendVerificationEmail(newAdmin.id);
- return {
- status: "success",
- type: "cloud",
- };
- }
- const session = await lucia.createSession(newAdmin.id || "", {});
- ctx.res.appendHeader(
- "Set-Cookie",
- lucia.createSessionCookie(session.id).serialize(),
- );
- return {
- status: "success",
- type: "selfhosted",
- };
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- // @ts-ignore
- message: `Error: ${error?.code === "23505" ? "Email already exists" : "Error creating admin"}`,
- cause: error,
- });
- }
- }),
- createUser: publicProcedure
- .input(apiCreateUser)
- .mutation(async ({ ctx, input }) => {
- try {
- const token = await getUserByToken(input.token);
- if (token.isExpired) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Invalid token",
- });
- }
-
- const newUser = await createUser(input);
-
- if (IS_CLOUD) {
- await sendVerificationEmail(token.authId);
- return true;
- }
- const session = await lucia.createSession(newUser?.authId || "", {});
- ctx.res.appendHeader(
- "Set-Cookie",
- lucia.createSessionCookie(session.id).serialize(),
- );
- return true;
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the user",
- cause: error,
- });
- }
- }),
-
- login: publicProcedure.input(apiLogin).mutation(async ({ ctx, input }) => {
- try {
- const auth = await findAuthByEmail(input.email);
-
- const correctPassword = bcrypt.compareSync(
- input.password,
- auth?.password || "",
- );
-
- if (!correctPassword) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Credentials do not match",
- });
- }
-
- if (auth?.confirmationToken && IS_CLOUD) {
- await sendVerificationEmail(auth.id);
- throw new TRPCError({
- code: "BAD_REQUEST",
- message:
- "Email not confirmed, we have sent you a confirmation email please check your inbox.",
- });
- }
-
- if (auth?.is2FAEnabled) {
- return {
- is2FAEnabled: true,
- authId: auth.id,
- };
- }
-
- const session = await lucia.createSession(auth?.id || "", {});
-
- ctx.res.appendHeader(
- "Set-Cookie",
- lucia.createSessionCookie(session.id).serialize(),
- );
- return {
- is2FAEnabled: false,
- authId: auth?.id,
- };
- } catch (error) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: `Error: ${error instanceof Error ? error.message : "Login error"}`,
- cause: error,
- });
- }
- }),
-
- get: protectedProcedure.query(async ({ ctx }) => {
- const auth = await findAuthById(ctx.user.authId);
- return auth;
- }),
-
- logout: protectedProcedure.mutation(async ({ ctx }) => {
- 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());
- return true;
- }),
-
- update: protectedProcedure
- .input(apiUpdateAuth)
- .mutation(async ({ ctx, input }) => {
- const currentAuth = await findAuthByEmail(ctx.user.email);
-
- if (input.currentPassword || input.password) {
- const correctPassword = bcrypt.compareSync(
- input.currentPassword || "",
- 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 && {
- password: bcrypt.hashSync(input.password, 10),
- }),
- ...(input.image && { image: input.image }),
- });
-
- 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);
-
- if (auth.token) {
- await luciaToken.invalidateSession(auth.token);
- }
- const session = await luciaToken.createSession(auth?.id || "", {
- expiresIn: 60 * 60 * 24 * 30,
- });
-
- await updateAuthById(auth.id, {
- token: session.id,
- });
-
- return auth;
- }),
- verifyToken: protectedProcedure.mutation(async () => {
- return true;
- }),
- one: adminProcedure.input(apiFindOneAuth).query(async ({ input }) => {
- const auth = await findAuthById(input.id);
- return auth;
- }),
-
- generate2FASecret: protectedProcedure.query(async ({ ctx }) => {
- return await generate2FASecret(ctx.user.authId);
- }),
- verify2FASetup: protectedProcedure
- .input(apiVerify2FA)
- .mutation(async ({ ctx, input }) => {
- const auth = await findAuthById(ctx.user.authId);
-
- await verify2FA(auth, input.secret, input.pin);
- await updateAuthById(auth.id, {
- is2FAEnabled: true,
- secret: input.secret,
- });
- return auth;
- }),
-
- verifyLogin2FA: publicProcedure
- .input(apiVerifyLogin2FA)
- .mutation(async ({ ctx, input }) => {
- const auth = await findAuthById(input.id);
-
- await verify2FA(auth, auth.secret || "", input.pin);
-
- const session = await lucia.createSession(auth.id, {});
-
- ctx.res.appendHeader(
- "Set-Cookie",
- lucia.createSessionCookie(session.id).serialize(),
- );
-
- return true;
- }),
- disable2FA: protectedProcedure.mutation(async ({ ctx }) => {
- const auth = await findAuthById(ctx.user.authId);
- await updateAuthById(auth.id, {
- is2FAEnabled: false,
- secret: null,
- });
- return auth;
- }),
- sendResetPasswordEmail: publicProcedure
- .input(
- z.object({
- email: z.string().min(1).email(),
- }),
- )
- .mutation(async ({ ctx, input }) => {
- if (!IS_CLOUD) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "This feature is only available in the cloud version",
- });
- }
- const authR = await db.query.auth.findFirst({
- where: eq(auth.email, input.email),
- });
- if (!authR) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "User not found",
- });
- }
- const token = nanoid();
- await updateAuthById(authR.id, {
- resetPasswordToken: token,
- // Make resetPassword in 24 hours
- resetPasswordExpiresAt: new Date(
- new Date().getTime() + 24 * 60 * 60 * 1000,
- ).toISOString(),
- });
-
- await sendEmailNotification(
- {
- fromAddress: process.env.SMTP_FROM_ADDRESS!,
- toAddresses: [authR.email],
- smtpServer: process.env.SMTP_SERVER!,
- smtpPort: Number(process.env.SMTP_PORT),
- username: process.env.SMTP_USERNAME!,
- password: process.env.SMTP_PASSWORD!,
- },
- "Reset Password",
- `
- Reset your password by clicking the link below:
- The link will expire in 24 hours.
-
- Reset Password
-
-
- `,
- );
- }),
-
- resetPassword: publicProcedure
- .input(
- z.object({
- resetPasswordToken: z.string().min(1),
- 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 authR = await db.query.auth.findFirst({
- where: eq(auth.resetPasswordToken, input.resetPasswordToken),
- });
-
- if (!authR || authR.resetPasswordExpiresAt === null) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Token not found",
- });
- }
-
- const isExpired = isBefore(
- new Date(authR.resetPasswordExpiresAt),
- new Date(),
- );
-
- if (isExpired) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Token expired",
- });
- }
-
- await updateAuthById(authR.id, {
- resetPasswordExpiresAt: null,
- resetPasswordToken: null,
- password: bcrypt.hashSync(input.password, 10),
- });
-
- return true;
- }),
- confirmEmail: adminProcedure
- .input(
- z.object({
- confirmationToken: z.string().min(1),
- }),
- )
- .mutation(async ({ ctx, input }) => {
- if (!IS_CLOUD) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Functionality not available in cloud version",
- });
- }
- const authR = await db.query.auth.findFirst({
- where: eq(auth.confirmationToken, input.confirmationToken),
- });
- if (!authR || authR.confirmationExpiresAt === null) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Token not found",
- });
- }
- if (authR.confirmationToken !== input.confirmationToken) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Confirmation Token not found",
- });
- }
-
- const isExpired = isBefore(
- new Date(authR.confirmationExpiresAt),
- new Date(),
- );
-
- if (isExpired) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Confirmation Token expired",
- });
- }
- 1;
- await updateAuthById(authR.id, {
- confirmationToken: null,
- confirmationExpiresAt: null,
- });
- return true;
- }),
+ // createAdmin: publicProcedure.mutation(async ({ input }) => {
+ // try {
+ // if (!IS_CLOUD) {
+ // const admin = await db.query.admins.findFirst({});
+ // if (admin) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: "Admin already exists",
+ // });
+ // }
+ // }
+ // const newAdmin = await createAdmin(input);
+ // if (IS_CLOUD) {
+ // await sendDiscordNotificationWelcome(newAdmin);
+ // await sendVerificationEmail(newAdmin.id);
+ // return {
+ // status: "success",
+ // type: "cloud",
+ // };
+ // }
+ // // const session = await lucia.createSession(newAdmin.id || "", {});
+ // // ctx.res.appendHeader(
+ // // "Set-Cookie",
+ // // lucia.createSessionCookie(session.id).serialize(),
+ // // );
+ // return {
+ // status: "success",
+ // type: "selfhosted",
+ // };
+ // } catch (error) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // // @ts-ignore
+ // message: `Error: ${error?.code === "23505" ? "Email already exists" : "Error creating admin"}`,
+ // cause: error,
+ // });
+ // }
+ // }),
+ // createUser: publicProcedure.mutation(async ({ input }) => {
+ // try {
+ // const _token = await getUserByToken(input.token);
+ // // if (token.isExpired) {
+ // // throw new TRPCError({
+ // // code: "BAD_REQUEST",
+ // // message: "Invalid token",
+ // // });
+ // // }
+ // // const newUser = await createUser(input);
+ // // if (IS_CLOUD) {
+ // // await sendVerificationEmail(token.authId);
+ // // return true;
+ // // }
+ // // const session = await lucia.createSession(newUser?.authId || "", {});
+ // // ctx.res.appendHeader(
+ // // "Set-Cookie",
+ // // lucia.createSessionCookie(session.id).serialize(),
+ // // );
+ // return true;
+ // } catch (error) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: "Error creating the user",
+ // cause: error,
+ // });
+ // }
+ // }),
+ // login: publicProcedure.mutation(async ({ input }) => {
+ // try {
+ // const auth = await findAuthByEmail(input.email);
+ // const correctPassword = bcrypt.compareSync(
+ // input.password,
+ // auth?.password || "",
+ // );
+ // if (!correctPassword) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: "Credentials do not match",
+ // });
+ // }
+ // if (auth?.confirmationToken && IS_CLOUD) {
+ // await sendVerificationEmail(auth.id);
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message:
+ // "Email not confirmed, we have sent you a confirmation email please check your inbox.",
+ // });
+ // }
+ // if (auth?.is2FAEnabled) {
+ // return {
+ // is2FAEnabled: true,
+ // authId: auth.id,
+ // };
+ // }
+ // // const session = await lucia.createSession(auth?.id || "", {});
+ // // ctx.res.appendHeader(
+ // // "Set-Cookie",
+ // // lucia.createSessionCookie(session.id).serialize(),
+ // // );
+ // return {
+ // is2FAEnabled: false,
+ // authId: auth?.id,
+ // };
+ // } catch (error) {
+ // throw new TRPCError({
+ // code: "BAD_REQUEST",
+ // message: `Error: ${error instanceof Error ? error.message : "Login error"}`,
+ // cause: error,
+ // });
+ // }
+ // }),
+ // get: protectedProcedure.query(async ({ ctx }) => {
+ // const memberResult = await db.query.member.findFirst({
+ // where: and(
+ // eq(member.userId, ctx.user.id),
+ // eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
+ // ),
+ // with: {
+ // user: true,
+ // },
+ // });
+ // return memberResult;
+ // }),
+ // logout: protectedProcedure.mutation(async ({ ctx }) => {
+ // const { req } = ctx;
+ // const { session } = await validateRequest(req);
+ // if (!session) return false;
+ // // await lucia.invalidateSession(session.id);
+ // // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
+ // return true;
+ // }),
+ // update: protectedProcedure.mutation(async ({ ctx, input }) => {
+ // const currentAuth = await findAuthByEmail(ctx.user.email);
+ // if (input.currentPassword || input.password) {
+ // const correctPassword = bcrypt.compareSync(
+ // input.currentPassword || "",
+ // 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 && {
+ // // password: bcrypt.hashSync(input.password, 10),
+ // // }),
+ // // ...(input.image && { image: input.image }),
+ // // });
+ // 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 } = ctx;
+ // const { session } = await validateRequest(req);
+ // if (!session) return false;
+ // // await lucia.invalidateSession(session.id);
+ // // res.setHeader("Set-Cookie", lucia.createBlankSessionCookie().serialize());
+ // // if (ctx.user.rol === "owner") {
+ // // await removeAdminByAuthId(ctx.user.authId);
+ // // } else {
+ // // await removeUserByAuthId(ctx.user.authId);
+ // // }
+ // return true;
+ // }),
+ // generateToken: protectedProcedure.mutation(async ({ ctx }) => {
+ // const auth = await findUserById(ctx.user.id);
+ // console.log(auth);
+ // // if (auth.token) {
+ // // await luciaToken.invalidateSession(auth.token);
+ // // }
+ // // const session = await luciaToken.createSession(auth?.id || "", {
+ // // expiresIn: 60 * 60 * 24 * 30,
+ // // });
+ // // await updateUser(auth.id, {
+ // // token: session.id,
+ // // });
+ // return auth;
+ // }),
+ // verifyToken: protectedProcedure.mutation(async () => {
+ // return true;
+ // }),
+ // one: adminProcedure
+ // .input(z.object({ userId: z.string().min(1) }))
+ // .query(async ({ input }) => {
+ // // TODO: Check if the user is admin or member
+ // const user = await findUserById(input.userId);
+ // return user;
+ // }),
+ // sendResetPasswordEmail: publicProcedure
+ // .input(
+ // z.object({
+ // email: z.string().min(1).email(),
+ // }),
+ // )
+ // .mutation(async ({ input }) => {
+ // if (!IS_CLOUD) {
+ // throw new TRPCError({
+ // code: "NOT_FOUND",
+ // message: "This feature is only available in the cloud version",
+ // });
+ // }
+ // const authR = await db.query.auth.findFirst({
+ // where: eq(auth.email, input.email),
+ // });
+ // if (!authR) {
+ // throw new TRPCError({
+ // code: "NOT_FOUND",
+ // message: "User not found",
+ // });
+ // }
+ // const token = nanoid();
+ // await updateAuthById(authR.id, {
+ // resetPasswordToken: token,
+ // // Make resetPassword in 24 hours
+ // resetPasswordExpiresAt: new Date(
+ // new Date().getTime() + 24 * 60 * 60 * 1000,
+ // ).toISOString(),
+ // });
+ // await sendEmailNotification(
+ // {
+ // fromAddress: process.env.SMTP_FROM_ADDRESS!,
+ // toAddresses: [authR.email],
+ // smtpServer: process.env.SMTP_SERVER!,
+ // smtpPort: Number(process.env.SMTP_PORT),
+ // username: process.env.SMTP_USERNAME!,
+ // password: process.env.SMTP_PASSWORD!,
+ // },
+ // "Reset Password",
+ // `
+ // Reset your password by clicking the link below:
+ // The link will expire in 24 hours.
+ //
+ // Reset Password
+ //
+ // `,
+ // );
+ // }),
});
-export const sendVerificationEmail = async (authId: string) => {
- const token = nanoid();
- const result = await updateAuthById(authId, {
- confirmationToken: token,
- confirmationExpiresAt: new Date(
- new Date().getTime() + 24 * 60 * 60 * 1000,
- ).toISOString(),
- });
+// export const sendVerificationEmail = async (authId: string) => {
+// const token = nanoid();
+// const result = await updateAuthById(authId, {
+// confirmationToken: token,
+// confirmationExpiresAt: new Date(
+// new Date().getTime() + 24 * 60 * 60 * 1000,
+// ).toISOString(),
+// });
- if (!result) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "User not found",
- });
- }
- await sendEmailNotification(
- {
- fromAddress: process.env.SMTP_FROM_ADDRESS || "",
- toAddresses: [result?.email],
- smtpServer: process.env.SMTP_SERVER || "",
- smtpPort: Number(process.env.SMTP_PORT),
- username: process.env.SMTP_USERNAME || "",
- password: process.env.SMTP_PASSWORD || "",
- },
- "Confirm your email | Dokploy",
- `
- Welcome to Dokploy!
- Please confirm your email by clicking the link below:
-
- Confirm Email
-
- `,
- );
+// if (!result) {
+// throw new TRPCError({
+// code: "BAD_REQUEST",
+// message: "User not found",
+// });
+// }
+// await sendEmailNotification(
+// {
+// fromAddress: process.env.SMTP_FROM_ADDRESS || "",
+// toAddresses: [result?.email],
+// smtpServer: process.env.SMTP_SERVER || "",
+// smtpPort: Number(process.env.SMTP_PORT),
+// username: process.env.SMTP_USERNAME || "",
+// password: process.env.SMTP_PASSWORD || "",
+// },
+// "Confirm your email | Dokploy",
+// `
+// Welcome to Dokploy!
+// Please confirm your email by clicking the link below:
+//
+// Confirm Email
+//
+// `,
+// );
- return true;
-};
+// return true;
+// };
-export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
- await sendDiscordNotification(
- {
- webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
- },
- {
- title: "New User Registered",
- color: 0x00ff00,
- fields: [
- {
- name: "Email",
- value: newAdmin.email,
- inline: true,
- },
- ],
- timestamp: newAdmin.createdAt,
- footer: {
- text: "Dokploy User Registration Notification",
- },
- },
- );
-};
+// export const sendDiscordNotificationWelcome = async (newAdmin: Auth) => {
+// await sendDiscordNotification(
+// {
+// webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
+// },
+// {
+// title: "New User Registered",
+// color: 0x00ff00,
+// fields: [
+// {
+// name: "Email",
+// value: newAdmin.email,
+// inline: true,
+// },
+// ],
+// timestamp: newAdmin.createdAt,
+// footer: {
+// text: "Dokploy User Registration Notification",
+// },
+// },
+// );
+// };
diff --git a/apps/dokploy/server/api/routers/backup.ts b/apps/dokploy/server/api/routers/backup.ts
index 0b8d7ab15..8a7a5f22f 100644
--- a/apps/dokploy/server/api/routers/backup.ts
+++ b/apps/dokploy/server/api/routers/backup.ts
@@ -30,7 +30,7 @@ import { TRPCError } from "@trpc/server";
export const backupRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateBackup)
- .mutation(async ({ input, ctx }) => {
+ .mutation(async ({ input }) => {
try {
const newBackup = await createBackup(input);
@@ -74,16 +74,14 @@ export const backupRouter = createTRPCRouter({
});
}
}),
- one: protectedProcedure
- .input(apiFindOneBackup)
- .query(async ({ input, ctx }) => {
- const backup = await findBackupById(input.backupId);
+ one: protectedProcedure.input(apiFindOneBackup).query(async ({ input }) => {
+ const backup = await findBackupById(input.backupId);
- return backup;
- }),
+ return backup;
+ }),
update: protectedProcedure
.input(apiUpdateBackup)
- .mutation(async ({ input, ctx }) => {
+ .mutation(async ({ input }) => {
try {
await updateBackupById(input.backupId, input);
const backup = await findBackupById(input.backupId);
@@ -111,15 +109,17 @@ export const backupRouter = createTRPCRouter({
}
}
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error updating this Backup";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error updating this Backup",
+ message,
});
}
}),
remove: protectedProcedure
.input(apiRemoveBackup)
- .mutation(async ({ input, ctx }) => {
+ .mutation(async ({ input }) => {
try {
const value = await removeBackupById(input.backupId);
if (IS_CLOUD && value) {
@@ -133,10 +133,11 @@ export const backupRouter = createTRPCRouter({
}
return value;
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error deleting this Backup";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error deleting this Backup",
- cause: error,
+ message,
});
}
}),
@@ -149,11 +150,13 @@ export const backupRouter = createTRPCRouter({
await runPostgresBackup(postgres, backup);
return true;
} catch (error) {
- console.log(error);
+ const message =
+ error instanceof Error
+ ? error.message
+ : "Error running manual Postgres backup ";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error running manual Postgres backup ",
- cause: error,
+ message,
});
}
}),
diff --git a/apps/dokploy/server/api/routers/bitbucket.ts b/apps/dokploy/server/api/routers/bitbucket.ts
index c66716d30..fa02be8d6 100644
--- a/apps/dokploy/server/api/routers/bitbucket.ts
+++ b/apps/dokploy/server/api/routers/bitbucket.ts
@@ -8,7 +8,6 @@ import {
apiUpdateBitbucket,
} from "@/server/db/schema";
import {
- IS_CLOUD,
createBitbucket,
findBitbucketById,
getBitbucketBranches,
@@ -23,7 +22,7 @@ export const bitbucketRouter = createTRPCRouter({
.input(apiCreateBitbucket)
.mutation(async ({ input, ctx }) => {
try {
- return await createBitbucket(input, ctx.user.adminId);
+ return await createBitbucket(input, ctx.session.activeOrganizationId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -37,10 +36,9 @@ export const bitbucketRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
if (
- IS_CLOUD &&
- bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
+ bitbucketProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this bitbucket provider",
@@ -58,12 +56,11 @@ export const bitbucketRouter = createTRPCRouter({
},
});
- if (IS_CLOUD) {
- // TODO: mAyBe a rEfaCtoR 🤫
- result = result.filter(
- (provider) => provider.gitProvider.adminId === ctx.user.adminId,
- );
- }
+ result = result.filter(
+ (provider) =>
+ provider.gitProvider.organizationId ===
+ ctx.session.activeOrganizationId,
+ );
return result;
}),
@@ -72,10 +69,9 @@ export const bitbucketRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
if (
- IS_CLOUD &&
- bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
+ bitbucketProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this bitbucket provider",
@@ -90,10 +86,9 @@ export const bitbucketRouter = createTRPCRouter({
input.bitbucketId || "",
);
if (
- IS_CLOUD &&
- bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
+ bitbucketProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this bitbucket provider",
@@ -107,10 +102,9 @@ export const bitbucketRouter = createTRPCRouter({
try {
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
if (
- IS_CLOUD &&
- bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
+ bitbucketProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this bitbucket provider",
@@ -131,10 +125,9 @@ export const bitbucketRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const bitbucketProvider = await findBitbucketById(input.bitbucketId);
if (
- IS_CLOUD &&
- bitbucketProvider.gitProvider.adminId !== ctx.user.adminId
+ bitbucketProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this bitbucket provider",
@@ -142,7 +135,7 @@ export const bitbucketRouter = createTRPCRouter({
}
return await updateBitbucket(input.bitbucketId, {
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
}),
});
diff --git a/apps/dokploy/server/api/routers/certificate.ts b/apps/dokploy/server/api/routers/certificate.ts
index 0f8d6fd94..3dc944ac8 100644
--- a/apps/dokploy/server/api/routers/certificate.ts
+++ b/apps/dokploy/server/api/routers/certificate.ts
@@ -25,14 +25,14 @@ export const certificateRouter = createTRPCRouter({
message: "Please set a server to create a certificate",
});
}
- return await createCertificate(input, ctx.user.adminId);
+ return await createCertificate(input, ctx.session.activeOrganizationId);
}),
one: adminProcedure
.input(apiFindCertificate)
.query(async ({ input, ctx }) => {
const certificates = await findCertificateById(input.certificateId);
- if (IS_CLOUD && certificates.adminId !== ctx.user.adminId) {
+ if (certificates.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this certificate",
@@ -44,7 +44,7 @@ export const certificateRouter = createTRPCRouter({
.input(apiFindCertificate)
.mutation(async ({ input, ctx }) => {
const certificates = await findCertificateById(input.certificateId);
- if (IS_CLOUD && certificates.adminId !== ctx.user.adminId) {
+ if (certificates.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to delete this certificate",
@@ -55,8 +55,7 @@ export const certificateRouter = createTRPCRouter({
}),
all: adminProcedure.query(async ({ ctx }) => {
return await db.query.certificates.findMany({
- // TODO: Remove this line when the cloud version is ready
- ...(IS_CLOUD && { where: eq(certificates.adminId, ctx.user.adminId) }),
+ where: eq(certificates.organizationId, ctx.session.activeOrganizationId),
});
}),
});
diff --git a/apps/dokploy/server/api/routers/cluster.ts b/apps/dokploy/server/api/routers/cluster.ts
index 7ded632c6..0d8407576 100644
--- a/apps/dokploy/server/api/routers/cluster.ts
+++ b/apps/dokploy/server/api/routers/cluster.ts
@@ -40,7 +40,7 @@ export const clusterRouter = createTRPCRouter({
});
}
}),
- addWorker: protectedProcedure.query(async ({ input }) => {
+ addWorker: protectedProcedure.query(async () => {
if (IS_CLOUD) {
return {
command: "",
@@ -57,7 +57,7 @@ export const clusterRouter = createTRPCRouter({
version: docker_version.Version,
};
}),
- addManager: protectedProcedure.query(async ({ input }) => {
+ addManager: protectedProcedure.query(async () => {
if (IS_CLOUD) {
return {
command: "",
diff --git a/apps/dokploy/server/api/routers/compose.ts b/apps/dokploy/server/api/routers/compose.ts
index dab37e786..bae926d05 100644
--- a/apps/dokploy/server/api/routers/compose.ts
+++ b/apps/dokploy/server/api/routers/compose.ts
@@ -39,13 +39,14 @@ import {
createComposeByTemplate,
createDomain,
createMount,
- findAdminById,
findComposeById,
findDomainsByComposeId,
findProjectById,
findServerById,
+ findUserById,
loadServices,
randomizeComposeFile,
+ randomizeIsolatedDeploymentComposeFile,
removeCompose,
removeComposeDirectory,
removeDeploymentsByComposeId,
@@ -59,8 +60,13 @@ export const composeRouter = createTRPCRouter({
.input(apiCreateCompose)
.mutation(async ({ ctx, input }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -70,7 +76,7 @@ export const composeRouter = createTRPCRouter({
});
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
@@ -78,8 +84,12 @@ export const composeRouter = createTRPCRouter({
}
const newService = await createCompose(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newService.composeId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newService.composeId,
+ project.organizationId,
+ );
}
return newService;
@@ -91,12 +101,17 @@ export const composeRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindCompose)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.composeId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.composeId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -109,7 +124,7 @@ export const composeRouter = createTRPCRouter({
.input(apiUpdateCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this compose",
@@ -120,12 +135,20 @@ export const composeRouter = createTRPCRouter({
delete: protectedProcedure
.input(apiDeleteCompose)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.composeId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.composeId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const composeResult = await findComposeById(input.composeId);
- if (composeResult.project.adminId !== ctx.user.adminId) {
+ if (
+ composeResult.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this compose",
@@ -147,7 +170,7 @@ export const composeRouter = createTRPCRouter({
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return result[0];
@@ -156,7 +179,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to clean this compose",
@@ -169,7 +192,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFetchServices)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to load this compose",
@@ -183,7 +206,9 @@ export const composeRouter = createTRPCRouter({
try {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (
+ compose.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to fetch this compose",
@@ -208,7 +233,7 @@ export const composeRouter = createTRPCRouter({
.input(apiRandomizeCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to randomize this compose",
@@ -216,11 +241,26 @@ export const composeRouter = createTRPCRouter({
}
return await randomizeComposeFile(input.composeId, input.suffix);
}),
+ isolatedDeployment: protectedProcedure
+ .input(apiRandomizeCompose)
+ .mutation(async ({ input, ctx }) => {
+ const compose = await findComposeById(input.composeId);
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to randomize this compose",
+ });
+ }
+ return await randomizeIsolatedDeploymentComposeFile(
+ input.composeId,
+ input.suffix,
+ );
+ }),
getConvertedCompose: protectedProcedure
.input(apiFindCompose)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to get this compose",
@@ -238,7 +278,7 @@ export const composeRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this compose",
@@ -271,7 +311,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to redeploy this compose",
@@ -303,7 +343,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this compose",
@@ -317,7 +357,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this compose",
@@ -332,7 +372,7 @@ export const composeRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to get this compose",
@@ -345,7 +385,7 @@ export const composeRouter = createTRPCRouter({
.input(apiFindCompose)
.mutation(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to refresh this compose",
@@ -359,8 +399,13 @@ export const composeRouter = createTRPCRouter({
deployTemplate: protectedProcedure
.input(apiCreateComposeByTemplate)
.mutation(async ({ ctx, input }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -374,7 +419,7 @@ export const composeRouter = createTRPCRouter({
const generate = await loadTemplateModule(input.id as TemplatesKeys);
- const admin = await findAdminById(ctx.user.adminId);
+ const admin = await findUserById(ctx.user.ownerId);
let serverIp = admin.serverIp || "127.0.0.1";
const project = await findProjectById(input.projectId);
@@ -399,10 +444,15 @@ export const composeRouter = createTRPCRouter({
name: input.id,
sourceType: "raw",
appName: `${projectName}-${generatePassword(6)}`,
+ isolatedDeployment: true,
});
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, compose.composeId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ compose.composeId,
+ project.organizationId,
+ );
}
if (mounts && mounts?.length > 0) {
@@ -446,7 +496,7 @@ export const composeRouter = createTRPCRouter({
return templatesData;
}),
- getTags: protectedProcedure.query(async ({ input }) => {
+ getTags: protectedProcedure.query(async () => {
const allTags = templates.flatMap((template) => template.tags);
const uniqueTags = _.uniq(allTags);
return uniqueTags;
diff --git a/apps/dokploy/server/api/routers/deployment.ts b/apps/dokploy/server/api/routers/deployment.ts
index bf981c6d9..8d95c1218 100644
--- a/apps/dokploy/server/api/routers/deployment.ts
+++ b/apps/dokploy/server/api/routers/deployment.ts
@@ -19,7 +19,9 @@ export const deploymentRouter = createTRPCRouter({
.input(apiFindAllByApplication)
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -32,7 +34,7 @@ export const deploymentRouter = createTRPCRouter({
.input(apiFindAllByCompose)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -44,7 +46,7 @@ export const deploymentRouter = createTRPCRouter({
.input(apiFindAllByServer)
.query(async ({ input, ctx }) => {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this server",
diff --git a/apps/dokploy/server/api/routers/destination.ts b/apps/dokploy/server/api/routers/destination.ts
index d13928be8..f1d582c50 100644
--- a/apps/dokploy/server/api/routers/destination.ts
+++ b/apps/dokploy/server/api/routers/destination.ts
@@ -28,7 +28,10 @@ export const destinationRouter = createTRPCRouter({
.input(apiCreateDestination)
.mutation(async ({ input, ctx }) => {
try {
- return await createDestintation(input, ctx.user.adminId);
+ return await createDestintation(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -84,7 +87,7 @@ export const destinationRouter = createTRPCRouter({
.input(apiFindOneDestination)
.query(async ({ input, ctx }) => {
const destination = await findDestinationById(input.destinationId);
- if (destination.adminId !== ctx.user.adminId) {
+ if (destination.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this destination",
@@ -94,7 +97,7 @@ export const destinationRouter = createTRPCRouter({
}),
all: protectedProcedure.query(async ({ ctx }) => {
return await db.query.destinations.findMany({
- where: eq(destinations.adminId, ctx.user.adminId),
+ where: eq(destinations.organizationId, ctx.session.activeOrganizationId),
});
}),
remove: adminProcedure
@@ -103,7 +106,7 @@ export const destinationRouter = createTRPCRouter({
try {
const destination = await findDestinationById(input.destinationId);
- if (destination.adminId !== ctx.user.adminId) {
+ if (destination.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to delete this destination",
@@ -111,7 +114,7 @@ export const destinationRouter = createTRPCRouter({
}
return await removeDestinationById(
input.destinationId,
- ctx.user.adminId,
+ ctx.session.activeOrganizationId,
);
} catch (error) {
throw error;
@@ -122,7 +125,7 @@ export const destinationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const destination = await findDestinationById(input.destinationId);
- if (destination.adminId !== ctx.user.adminId) {
+ if (destination.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to update this destination",
@@ -130,7 +133,7 @@ export const destinationRouter = createTRPCRouter({
}
return await updateDestinationById(input.destinationId, {
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw error;
diff --git a/apps/dokploy/server/api/routers/domain.ts b/apps/dokploy/server/api/routers/domain.ts
index f122cf86f..aac2a016f 100644
--- a/apps/dokploy/server/api/routers/domain.ts
+++ b/apps/dokploy/server/api/routers/domain.ts
@@ -30,7 +30,9 @@ export const domainRouter = createTRPCRouter({
try {
if (input.domainType === "compose" && input.composeId) {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (
+ compose.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -38,7 +40,10 @@ export const domainRouter = createTRPCRouter({
}
} else if (input.domainType === "application" && input.applicationId) {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -58,7 +63,9 @@ export const domainRouter = createTRPCRouter({
.input(apiFindOneApplication)
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -70,7 +77,7 @@ export const domainRouter = createTRPCRouter({
.input(apiFindCompose)
.query(async ({ input, ctx }) => {
const compose = await findComposeById(input.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -83,7 +90,7 @@ export const domainRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
return generateTraefikMeDomain(
input.appName,
- ctx.user.adminId,
+ ctx.user.ownerId,
input.serverId,
);
}),
@@ -95,7 +102,9 @@ export const domainRouter = createTRPCRouter({
if (currentDomain.applicationId) {
const newApp = await findApplicationById(currentDomain.applicationId);
- if (newApp.project.adminId !== ctx.user.adminId) {
+ if (
+ newApp.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -103,7 +112,9 @@ export const domainRouter = createTRPCRouter({
}
} else if (currentDomain.composeId) {
const newCompose = await findComposeById(currentDomain.composeId);
- if (newCompose.project.adminId !== ctx.user.adminId) {
+ if (
+ newCompose.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -114,7 +125,8 @@ export const domainRouter = createTRPCRouter({
currentDomain.previewDeploymentId,
);
if (
- newPreviewDeployment.application.project.adminId !== ctx.user.adminId
+ newPreviewDeployment.application.project.organizationId !==
+ ctx.session.activeOrganizationId
) {
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -143,7 +155,9 @@ export const domainRouter = createTRPCRouter({
const domain = await findDomainById(input.domainId);
if (domain.applicationId) {
const application = await findApplicationById(domain.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -151,7 +165,7 @@ export const domainRouter = createTRPCRouter({
}
} else if (domain.composeId) {
const compose = await findComposeById(domain.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (compose.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
@@ -166,7 +180,10 @@ export const domainRouter = createTRPCRouter({
const domain = await findDomainById(input.domainId);
if (domain.applicationId) {
const application = await findApplicationById(domain.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -174,7 +191,9 @@ export const domainRouter = createTRPCRouter({
}
} else if (domain.composeId) {
const compose = await findComposeById(domain.composeId);
- if (compose.project.adminId !== ctx.user.adminId) {
+ if (
+ compose.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this compose",
diff --git a/apps/dokploy/server/api/routers/git-provider.ts b/apps/dokploy/server/api/routers/git-provider.ts
index abd933923..ed37869d5 100644
--- a/apps/dokploy/server/api/routers/git-provider.ts
+++ b/apps/dokploy/server/api/routers/git-provider.ts
@@ -1,11 +1,7 @@
import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import { db } from "@/server/db";
import { apiRemoveGitProvider, gitProvider } from "@/server/db/schema";
-import {
- IS_CLOUD,
- findGitProviderById,
- removeGitProvider,
-} from "@dokploy/server";
+import { findGitProviderById, removeGitProvider } from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { desc, eq } from "drizzle-orm";
@@ -18,8 +14,7 @@ export const gitProviderRouter = createTRPCRouter({
github: true,
},
orderBy: desc(gitProvider.createdAt),
- ...(IS_CLOUD && { where: eq(gitProvider.adminId, ctx.user.adminId) }),
- //TODO: Remove this line when the cloud version is ready
+ where: eq(gitProvider.organizationId, ctx.session.activeOrganizationId),
});
}),
remove: protectedProcedure
@@ -28,8 +23,7 @@ export const gitProviderRouter = createTRPCRouter({
try {
const gitProvider = await findGitProviderById(input.gitProviderId);
- if (IS_CLOUD && gitProvider.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (gitProvider.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to delete this Git provider",
@@ -37,9 +31,13 @@ export const gitProviderRouter = createTRPCRouter({
}
return await removeGitProvider(input.gitProviderId);
} catch (error) {
+ const message =
+ error instanceof Error
+ ? error.message
+ : "Error deleting this Git provider";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error deleting this Git provider",
+ message,
});
}
}),
diff --git a/apps/dokploy/server/api/routers/github.ts b/apps/dokploy/server/api/routers/github.ts
index 562225778..691030e27 100644
--- a/apps/dokploy/server/api/routers/github.ts
+++ b/apps/dokploy/server/api/routers/github.ts
@@ -6,7 +6,6 @@ import {
apiUpdateGithub,
} from "@/server/db/schema";
import {
- IS_CLOUD,
findGithubById,
getGithubBranches,
getGithubRepositories,
@@ -20,8 +19,10 @@ export const githubRouter = createTRPCRouter({
.input(apiFindOneGithub)
.query(async ({ input, ctx }) => {
const githubProvider = await findGithubById(input.githubId);
- if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ githubProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this github provider",
@@ -33,8 +34,10 @@ export const githubRouter = createTRPCRouter({
.input(apiFindOneGithub)
.query(async ({ input, ctx }) => {
const githubProvider = await findGithubById(input.githubId);
- if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ githubProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this github provider",
@@ -46,7 +49,10 @@ export const githubRouter = createTRPCRouter({
.input(apiFindGithubBranches)
.query(async ({ input, ctx }) => {
const githubProvider = await findGithubById(input.githubId || "");
- if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
+ if (
+ githubProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
//TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
@@ -62,12 +68,11 @@ export const githubRouter = createTRPCRouter({
},
});
- if (IS_CLOUD) {
- // TODO: mAyBe a rEfaCtoR 🤫
- result = result.filter(
- (provider) => provider.gitProvider.adminId === ctx.user.adminId,
- );
- }
+ result = result.filter(
+ (provider) =>
+ provider.gitProvider.organizationId ===
+ ctx.session.activeOrganizationId,
+ );
const filtered = result
.filter((provider) => haveGithubRequirements(provider))
@@ -89,10 +94,9 @@ export const githubRouter = createTRPCRouter({
try {
const githubProvider = await findGithubById(input.githubId);
if (
- IS_CLOUD &&
- githubProvider.gitProvider.adminId !== ctx.user.adminId
+ githubProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this github provider",
@@ -111,8 +115,10 @@ export const githubRouter = createTRPCRouter({
.input(apiUpdateGithub)
.mutation(async ({ input, ctx }) => {
const githubProvider = await findGithubById(input.githubId);
- if (IS_CLOUD && githubProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ githubProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this github provider",
@@ -120,7 +126,7 @@ export const githubRouter = createTRPCRouter({
}
await updateGitProvider(input.gitProviderId, {
name: input.name,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
}),
});
diff --git a/apps/dokploy/server/api/routers/gitlab.ts b/apps/dokploy/server/api/routers/gitlab.ts
index 6d35f4a28..daae68a5a 100644
--- a/apps/dokploy/server/api/routers/gitlab.ts
+++ b/apps/dokploy/server/api/routers/gitlab.ts
@@ -9,7 +9,6 @@ import {
import { db } from "@/server/db";
import {
- IS_CLOUD,
createGitlab,
findGitlabById,
getGitlabBranches,
@@ -26,7 +25,7 @@ export const gitlabRouter = createTRPCRouter({
.input(apiCreateGitlab)
.mutation(async ({ input, ctx }) => {
try {
- return await createGitlab(input, ctx.user.adminId);
+ return await createGitlab(input, ctx.session.activeOrganizationId);
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -39,8 +38,10 @@ export const gitlabRouter = createTRPCRouter({
.input(apiFindOneGitlab)
.query(async ({ input, ctx }) => {
const gitlabProvider = await findGitlabById(input.gitlabId);
- if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ gitlabProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitlab provider",
@@ -55,12 +56,11 @@ export const gitlabRouter = createTRPCRouter({
},
});
- if (IS_CLOUD) {
- // TODO: mAyBe a rEfaCtoR 🤫
- result = result.filter(
- (provider) => provider.gitProvider.adminId === ctx.user.adminId,
- );
- }
+ result = result.filter(
+ (provider) =>
+ provider.gitProvider.organizationId ===
+ ctx.session.activeOrganizationId,
+ );
const filtered = result
.filter((provider) => haveGitlabRequirements(provider))
.map((provider) => {
@@ -78,8 +78,10 @@ export const gitlabRouter = createTRPCRouter({
.input(apiFindOneGitlab)
.query(async ({ input, ctx }) => {
const gitlabProvider = await findGitlabById(input.gitlabId);
- if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ gitlabProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitlab provider",
@@ -92,8 +94,10 @@ export const gitlabRouter = createTRPCRouter({
.input(apiFindGitlabBranches)
.query(async ({ input, ctx }) => {
const gitlabProvider = await findGitlabById(input.gitlabId || "");
- if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ gitlabProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitlab provider",
@@ -107,10 +111,9 @@ export const gitlabRouter = createTRPCRouter({
try {
const gitlabProvider = await findGitlabById(input.gitlabId || "");
if (
- IS_CLOUD &&
- gitlabProvider.gitProvider.adminId !== ctx.user.adminId
+ gitlabProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
) {
- //TODO: Remove this line when the cloud version is ready
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitlab provider",
@@ -130,8 +133,10 @@ export const gitlabRouter = createTRPCRouter({
.input(apiUpdateGitlab)
.mutation(async ({ input, ctx }) => {
const gitlabProvider = await findGitlabById(input.gitlabId);
- if (IS_CLOUD && gitlabProvider.gitProvider.adminId !== ctx.user.adminId) {
- //TODO: Remove this line when the cloud version is ready
+ if (
+ gitlabProvider.gitProvider.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this Gitlab provider",
@@ -140,7 +145,7 @@ export const gitlabRouter = createTRPCRouter({
if (input.name) {
await updateGitProvider(input.gitProviderId, {
name: input.name,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
await updateGitlab(input.gitlabId, {
diff --git a/apps/dokploy/server/api/routers/mariadb.ts b/apps/dokploy/server/api/routers/mariadb.ts
index 09f4d6757..be0ffd39a 100644
--- a/apps/dokploy/server/api/routers/mariadb.ts
+++ b/apps/dokploy/server/api/routers/mariadb.ts
@@ -9,6 +9,7 @@ import {
apiSaveExternalPortMariaDB,
apiUpdateMariaDB,
} from "@/server/db/schema";
+import { cancelJobs } from "@/server/utils/backup";
import {
IS_CLOUD,
addNewService,
@@ -16,9 +17,9 @@ import {
createMariadb,
createMount,
deployMariadb,
+ findBackupsByDbId,
findMariadbById,
findProjectById,
- findServerById,
removeMariadbById,
removeService,
startService,
@@ -35,8 +36,13 @@ export const mariadbRouter = createTRPCRouter({
.input(apiCreateMariaDB)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -47,15 +53,19 @@ export const mariadbRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
const newMariadb = await createMariadb(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newMariadb.mariadbId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newMariadb.mariadbId,
+ project.organizationId,
+ );
}
await createMount({
@@ -77,11 +87,16 @@ export const mariadbRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneMariaDB)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mariadbId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mariadbId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this Mariadb",
@@ -94,7 +109,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiFindOneMariaDB)
.mutation(async ({ input, ctx }) => {
const service = await findMariadbById(input.mariadbId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this Mariadb",
@@ -131,7 +146,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiSaveExternalPortMariaDB)
.mutation(async ({ input, ctx }) => {
const mongo = await findMariadbById(input.mariadbId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -147,7 +162,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiDeployMariaDB)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Mariadb",
@@ -168,7 +183,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiDeployMariaDB)
.subscription(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Mariadb",
@@ -185,7 +200,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiChangeMariaDBStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findMariadbById(input.mariadbId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this Mariadb status",
@@ -199,27 +214,34 @@ export const mariadbRouter = createTRPCRouter({
remove: protectedProcedure
.input(apiFindOneMariaDB)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mariadbId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mariadbId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const mongo = await findMariadbById(input.mariadbId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this Mariadb",
});
}
+ const backups = await findBackupsByDbId(input.mariadbId, "mariadb");
const cleanupOperations = [
async () => await removeService(mongo?.appName, mongo.serverId),
+ async () => await cancelJobs(backups),
async () => await removeMariadbById(input.mariadbId),
];
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return mongo;
@@ -228,7 +250,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesMariaDB)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -251,7 +273,7 @@ export const mariadbRouter = createTRPCRouter({
.input(apiResetMariadb)
.mutation(async ({ input, ctx }) => {
const mariadb = await findMariadbById(input.mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this Mariadb",
@@ -281,7 +303,7 @@ export const mariadbRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { mariadbId, ...rest } = input;
const mariadb = await findMariadbById(mariadbId);
- if (mariadb.project.adminId !== ctx.user.adminId) {
+ if (mariadb.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this Mariadb",
diff --git a/apps/dokploy/server/api/routers/mongo.ts b/apps/dokploy/server/api/routers/mongo.ts
index b114b8d87..1c3ba6bb7 100644
--- a/apps/dokploy/server/api/routers/mongo.ts
+++ b/apps/dokploy/server/api/routers/mongo.ts
@@ -9,6 +9,7 @@ import {
apiSaveExternalPortMongo,
apiUpdateMongo,
} from "@/server/db/schema";
+import { cancelJobs } from "@/server/utils/backup";
import {
IS_CLOUD,
addNewService,
@@ -16,6 +17,7 @@ import {
createMongo,
createMount,
deployMongo,
+ findBackupsByDbId,
findMongoById,
findProjectById,
removeMongoById,
@@ -34,8 +36,13 @@ export const mongoRouter = createTRPCRouter({
.input(apiCreateMongo)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -46,15 +53,19 @@ export const mongoRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
const newMongo = await createMongo(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newMongo.mongoId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newMongo.mongoId,
+ project.organizationId,
+ );
}
await createMount({
@@ -80,12 +91,17 @@ export const mongoRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneMongo)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mongoId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mongoId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this mongo",
@@ -99,7 +115,7 @@ export const mongoRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const service = await findMongoById(input.mongoId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this mongo",
@@ -122,7 +138,7 @@ export const mongoRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this mongo",
@@ -144,7 +160,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiSaveExternalPortMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -160,7 +176,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiDeployMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this mongo",
@@ -180,7 +196,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiDeployMongo)
.subscription(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this mongo",
@@ -197,7 +213,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiChangeMongoStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this mongo status",
@@ -212,7 +228,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiResetMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this mongo",
@@ -240,28 +256,35 @@ export const mongoRouter = createTRPCRouter({
remove: protectedProcedure
.input(apiFindOneMongo)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mongoId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mongoId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this mongo",
});
}
+ const backups = await findBackupsByDbId(input.mongoId, "mongo");
const cleanupOperations = [
async () => await removeService(mongo?.appName, mongo.serverId),
+ async () => await cancelJobs(backups),
async () => await removeMongoById(input.mongoId),
];
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return mongo;
@@ -270,7 +293,7 @@ export const mongoRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesMongo)
.mutation(async ({ input, ctx }) => {
const mongo = await findMongoById(input.mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -294,7 +317,7 @@ export const mongoRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { mongoId, ...rest } = input;
const mongo = await findMongoById(mongoId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this mongo",
diff --git a/apps/dokploy/server/api/routers/mysql.ts b/apps/dokploy/server/api/routers/mysql.ts
index 44d2537aa..594403f24 100644
--- a/apps/dokploy/server/api/routers/mysql.ts
+++ b/apps/dokploy/server/api/routers/mysql.ts
@@ -12,6 +12,7 @@ import {
import { TRPCError } from "@trpc/server";
+import { cancelJobs } from "@/server/utils/backup";
import {
IS_CLOUD,
addNewService,
@@ -19,6 +20,7 @@ import {
createMount,
createMysql,
deployMySql,
+ findBackupsByDbId,
findMySqlById,
findProjectById,
removeMySqlById,
@@ -36,8 +38,13 @@ export const mysqlRouter = createTRPCRouter({
.input(apiCreateMySql)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -48,7 +55,7 @@ export const mysqlRouter = createTRPCRouter({
}
1;
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
@@ -56,8 +63,12 @@ export const mysqlRouter = createTRPCRouter({
}
const newMysql = await createMysql(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newMysql.mysqlId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newMysql.mysqlId,
+ project.organizationId,
+ );
}
await createMount({
@@ -83,11 +94,16 @@ export const mysqlRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneMySql)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mysqlId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mysqlId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this MySQL",
@@ -100,7 +116,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiFindOneMySql)
.mutation(async ({ input, ctx }) => {
const service = await findMySqlById(input.mysqlId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this MySQL",
@@ -122,7 +138,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiFindOneMySql)
.mutation(async ({ input, ctx }) => {
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this MySQL",
@@ -143,7 +159,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiSaveExternalPortMySql)
.mutation(async ({ input, ctx }) => {
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -159,7 +175,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiDeployMySql)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this MySQL",
@@ -179,7 +195,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiDeployMySql)
.subscription(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this MySQL",
@@ -196,7 +212,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiChangeMySqlStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this MySQL status",
@@ -211,7 +227,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiResetMysql)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this MySQL",
@@ -238,26 +254,33 @@ export const mysqlRouter = createTRPCRouter({
remove: protectedProcedure
.input(apiFindOneMySql)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.mysqlId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.mysqlId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const mongo = await findMySqlById(input.mysqlId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this MySQL",
});
}
+ const backups = await findBackupsByDbId(input.mysqlId, "mysql");
const cleanupOperations = [
async () => await removeService(mongo?.appName, mongo.serverId),
+ async () => await cancelJobs(backups),
async () => await removeMySqlById(input.mysqlId),
];
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return mongo;
@@ -266,7 +289,7 @@ export const mysqlRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesMySql)
.mutation(async ({ input, ctx }) => {
const mysql = await findMySqlById(input.mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -290,7 +313,7 @@ export const mysqlRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { mysqlId, ...rest } = input;
const mysql = await findMySqlById(mysqlId);
- if (mysql.project.adminId !== ctx.user.adminId) {
+ if (mysql.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this MySQL",
diff --git a/apps/dokploy/server/api/routers/notification.ts b/apps/dokploy/server/api/routers/notification.ts
index 2eafc66d2..23283d971 100644
--- a/apps/dokploy/server/api/routers/notification.ts
+++ b/apps/dokploy/server/api/routers/notification.ts
@@ -23,6 +23,8 @@ import {
apiUpdateSlack,
apiUpdateTelegram,
notifications,
+ server,
+ users_temp,
} from "@/server/db/schema";
import {
IS_CLOUD,
@@ -36,6 +38,7 @@ import {
sendDiscordNotification,
sendEmailNotification,
sendGotifyNotification,
+ sendServerThresholdNotifications,
sendSlackNotification,
sendTelegramNotification,
updateDiscordNotification,
@@ -45,15 +48,18 @@ import {
updateTelegramNotification,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
-import { desc, eq } from "drizzle-orm";
+import { desc, eq, sql } from "drizzle-orm";
+import { z } from "zod";
-// TODO: Uncomment the validations when is cloud ready
export const notificationRouter = createTRPCRouter({
createSlack: adminProcedure
.input(apiCreateSlack)
.mutation(async ({ input, ctx }) => {
try {
- return await createSlackNotification(input, ctx.user.adminId);
+ return await createSlackNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -67,8 +73,7 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this notification",
@@ -76,7 +81,7 @@ export const notificationRouter = createTRPCRouter({
}
return await updateSlackNotification({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw error;
@@ -103,7 +108,10 @@ export const notificationRouter = createTRPCRouter({
.input(apiCreateTelegram)
.mutation(async ({ input, ctx }) => {
try {
- return await createTelegramNotification(input, ctx.user.adminId);
+ return await createTelegramNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -118,8 +126,7 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this notification",
@@ -127,7 +134,7 @@ export const notificationRouter = createTRPCRouter({
}
return await updateTelegramNotification({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw new TRPCError({
@@ -155,7 +162,10 @@ export const notificationRouter = createTRPCRouter({
.input(apiCreateDiscord)
.mutation(async ({ input, ctx }) => {
try {
- return await createDiscordNotification(input, ctx.user.adminId);
+ return await createDiscordNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -170,8 +180,7 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this notification",
@@ -179,7 +188,7 @@ export const notificationRouter = createTRPCRouter({
}
return await updateDiscordNotification({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw new TRPCError({
@@ -216,7 +225,10 @@ export const notificationRouter = createTRPCRouter({
.input(apiCreateEmail)
.mutation(async ({ input, ctx }) => {
try {
- return await createEmailNotification(input, ctx.user.adminId);
+ return await createEmailNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -230,8 +242,7 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this notification",
@@ -239,7 +250,7 @@ export const notificationRouter = createTRPCRouter({
}
return await updateEmailNotification({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw new TRPCError({
@@ -272,8 +283,7 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this notification",
@@ -281,9 +291,13 @@ export const notificationRouter = createTRPCRouter({
}
return await removeNotificationById(input.notificationId);
} catch (error) {
+ const message =
+ error instanceof Error
+ ? error.message
+ : "Error deleting this notification";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error deleting this notification",
+ message,
});
}
}),
@@ -291,8 +305,7 @@ export const notificationRouter = createTRPCRouter({
.input(apiFindOneNotification)
.query(async ({ input, ctx }) => {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (notification.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this notification",
@@ -310,15 +323,81 @@ export const notificationRouter = createTRPCRouter({
gotify: true,
},
orderBy: desc(notifications.createdAt),
- ...(IS_CLOUD && { where: eq(notifications.adminId, ctx.user.adminId) }),
- // TODO: Remove this line when the cloud version is ready
+ where: eq(notifications.organizationId, ctx.session.activeOrganizationId),
});
}),
+ receiveNotification: publicProcedure
+ .input(
+ z.object({
+ ServerType: z.enum(["Dokploy", "Remote"]).default("Dokploy"),
+ Type: z.enum(["Memory", "CPU"]),
+ Value: z.number(),
+ Threshold: z.number(),
+ Message: z.string(),
+ Timestamp: z.string(),
+ Token: z.string(),
+ }),
+ )
+ .mutation(async ({ input }) => {
+ try {
+ let organizationId = "";
+ let ServerName = "";
+ if (input.ServerType === "Dokploy") {
+ const result = await db
+ .select()
+ .from(users_temp)
+ .where(
+ sql`${users_temp.metricsConfig}::jsonb -> 'server' ->> 'token' = ${input.Token}`,
+ );
+
+ if (!result?.[0]?.id) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Token not found",
+ });
+ }
+
+ organizationId = result?.[0]?.id;
+ ServerName = "Dokploy";
+ } else {
+ const result = await db
+ .select()
+ .from(server)
+ .where(
+ sql`${server.metricsConfig}::jsonb -> 'server' ->> 'token' = ${input.Token}`,
+ );
+
+ if (!result?.[0]?.organizationId) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Token not found",
+ });
+ }
+
+ organizationId = result?.[0]?.organizationId;
+ ServerName = "Remote";
+ }
+
+ await sendServerThresholdNotifications(organizationId, {
+ ...input,
+ ServerName,
+ });
+ } catch (error) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Error sending the notification",
+ cause: error,
+ });
+ }
+ }),
createGotify: adminProcedure
.input(apiCreateGotify)
.mutation(async ({ input, ctx }) => {
try {
- return await createGotifyNotification(input, ctx.user.adminId);
+ return await createGotifyNotification(
+ input,
+ ctx.session.activeOrganizationId,
+ );
} catch (error) {
throw new TRPCError({
code: "BAD_REQUEST",
@@ -332,7 +411,10 @@ export const notificationRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const notification = await findNotificationById(input.notificationId);
- if (IS_CLOUD && notification.adminId !== ctx.user.adminId) {
+ if (
+ IS_CLOUD &&
+ notification.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this notification",
@@ -340,7 +422,7 @@ export const notificationRouter = createTRPCRouter({
}
return await updateGotifyNotification({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw error;
diff --git a/apps/dokploy/server/api/routers/organization.ts b/apps/dokploy/server/api/routers/organization.ts
new file mode 100644
index 000000000..6f7a9c67d
--- /dev/null
+++ b/apps/dokploy/server/api/routers/organization.ts
@@ -0,0 +1,148 @@
+import { db } from "@/server/db";
+import { invitation, member, organization } from "@/server/db/schema";
+import { IS_CLOUD } from "@dokploy/server/index";
+import { TRPCError } from "@trpc/server";
+import { and, desc, eq, exists } from "drizzle-orm";
+import { nanoid } from "nanoid";
+import { z } from "zod";
+import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
+export const organizationRouter = createTRPCRouter({
+ create: protectedProcedure
+ .input(
+ z.object({
+ name: z.string(),
+ logo: z.string().optional(),
+ }),
+ )
+ .mutation(async ({ ctx, input }) => {
+ if (ctx.user.rol !== "owner" && !IS_CLOUD) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Only the organization owner can create an organization",
+ });
+ }
+ const result = await db
+ .insert(organization)
+ .values({
+ ...input,
+ slug: nanoid(),
+ createdAt: new Date(),
+ ownerId: ctx.user.id,
+ })
+ .returning()
+ .then((res) => res[0]);
+
+ console.log("result", result);
+
+ if (!result) {
+ throw new TRPCError({
+ code: "INTERNAL_SERVER_ERROR",
+ message: "Failed to create organization",
+ });
+ }
+
+ await db.insert(member).values({
+ organizationId: result.id,
+ role: "owner",
+ createdAt: new Date(),
+ userId: ctx.user.id,
+ });
+ return result;
+ }),
+ all: protectedProcedure.query(async ({ ctx }) => {
+ const memberResult = await db.query.organization.findMany({
+ where: (organization) =>
+ exists(
+ db
+ .select()
+ .from(member)
+ .where(
+ and(
+ eq(member.organizationId, organization.id),
+ eq(member.userId, ctx.user.id),
+ ),
+ ),
+ ),
+ });
+ return memberResult;
+ }),
+ one: protectedProcedure
+ .input(
+ z.object({
+ organizationId: z.string(),
+ }),
+ )
+ .query(async ({ input }) => {
+ return await db.query.organization.findFirst({
+ where: eq(organization.id, input.organizationId),
+ });
+ }),
+ update: protectedProcedure
+ .input(
+ z.object({
+ organizationId: z.string(),
+ name: z.string(),
+ logo: z.string().optional(),
+ }),
+ )
+ .mutation(async ({ ctx, input }) => {
+ if (ctx.user.rol !== "owner" && !IS_CLOUD) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Only the organization owner can update it",
+ });
+ }
+ const result = await db
+ .update(organization)
+ .set({
+ name: input.name,
+ logo: input.logo,
+ })
+ .where(eq(organization.id, input.organizationId))
+ .returning();
+ return result[0];
+ }),
+ delete: protectedProcedure
+ .input(
+ z.object({
+ organizationId: z.string(),
+ }),
+ )
+ .mutation(async ({ ctx, input }) => {
+ if (ctx.user.rol !== "owner" && !IS_CLOUD) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Only the organization owner can delete it",
+ });
+ }
+ const org = await db.query.organization.findFirst({
+ where: eq(organization.id, input.organizationId),
+ });
+
+ if (!org) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "Organization not found",
+ });
+ }
+
+ if (org.ownerId !== ctx.user.id) {
+ throw new TRPCError({
+ code: "FORBIDDEN",
+ message: "Only the organization owner can delete it",
+ });
+ }
+
+ const result = await db
+ .delete(organization)
+ .where(eq(organization.id, input.organizationId));
+
+ return result;
+ }),
+ allInvitations: adminProcedure.query(async ({ ctx }) => {
+ return await db.query.invitation.findMany({
+ where: eq(invitation.organizationId, ctx.session.activeOrganizationId),
+ orderBy: [desc(invitation.status), desc(invitation.expiresAt)],
+ });
+ }),
+});
diff --git a/apps/dokploy/server/api/routers/port.ts b/apps/dokploy/server/api/routers/port.ts
index bfbc98633..923fea573 100644
--- a/apps/dokploy/server/api/routers/port.ts
+++ b/apps/dokploy/server/api/routers/port.ts
@@ -44,9 +44,11 @@ export const portRouter = createTRPCRouter({
try {
return removePortById(input.portId);
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error input: Deleting port";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error input: Deleting port",
+ message,
});
}
}),
@@ -56,9 +58,11 @@ export const portRouter = createTRPCRouter({
try {
return updatePortById(input.portId, input);
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error updating the port";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error updating the port",
+ message,
});
}
}),
diff --git a/apps/dokploy/server/api/routers/postgres.ts b/apps/dokploy/server/api/routers/postgres.ts
index 7d1789433..cf3221b48 100644
--- a/apps/dokploy/server/api/routers/postgres.ts
+++ b/apps/dokploy/server/api/routers/postgres.ts
@@ -1,9 +1,4 @@
-import { EventEmitter } from "node:events";
-import {
- createTRPCRouter,
- protectedProcedure,
- publicProcedure,
-} from "@/server/api/trpc";
+import { createTRPCRouter, protectedProcedure } from "@/server/api/trpc";
import {
apiChangePostgresStatus,
apiCreatePostgres,
@@ -14,6 +9,7 @@ import {
apiSaveExternalPortPostgres,
apiUpdatePostgres,
} from "@/server/db/schema";
+import { cancelJobs } from "@/server/utils/backup";
import {
IS_CLOUD,
addNewService,
@@ -21,6 +17,7 @@ import {
createMount,
createPostgres,
deployPostgres,
+ findBackupsByDbId,
findPostgresById,
findProjectById,
removePostgresById,
@@ -33,17 +30,19 @@ import {
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
-import { z } from "zod";
-
-const ee = new EventEmitter();
export const postgresRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreatePostgres)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -54,15 +53,19 @@ export const postgresRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
const newPostgres = await createPostgres(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newPostgres.postgresId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newPostgres.postgresId,
+ project.organizationId,
+ );
}
await createMount({
@@ -88,12 +91,19 @@ export const postgresRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOnePostgres)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.postgresId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.postgresId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this Postgres",
@@ -107,7 +117,7 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const service = await findPostgresById(input.postgresId);
- if (service.project.adminId !== ctx.user.adminId) {
+ if (service.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this Postgres",
@@ -129,7 +139,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiFindOnePostgres)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this Postgres",
@@ -151,7 +163,9 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -167,7 +181,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiDeployPostgres)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Postgres",
@@ -188,7 +204,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiDeployPostgres)
.subscription(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Postgres",
@@ -205,7 +223,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiChangePostgresStatus)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this Postgres status",
@@ -219,20 +239,30 @@ export const postgresRouter = createTRPCRouter({
remove: protectedProcedure
.input(apiFindOnePostgres)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.postgresId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.postgresId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this Postgres",
});
}
+ const backups = await findBackupsByDbId(input.postgresId, "postgres");
+
const cleanupOperations = [
removeService(postgres.appName, postgres.serverId),
+ cancelJobs(backups),
removePostgresById(input.postgresId),
];
@@ -244,7 +274,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesPostgres)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
@@ -267,7 +299,9 @@ export const postgresRouter = createTRPCRouter({
.input(apiResetPostgres)
.mutation(async ({ input, ctx }) => {
const postgres = await findPostgresById(input.postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this Postgres",
@@ -297,7 +331,9 @@ export const postgresRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { postgresId, ...rest } = input;
const postgres = await findPostgresById(postgresId);
- if (postgres.project.adminId !== ctx.user.adminId) {
+ if (
+ postgres.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this Postgres",
diff --git a/apps/dokploy/server/api/routers/preview-deployment.ts b/apps/dokploy/server/api/routers/preview-deployment.ts
index 74b8461ae..f833e9f95 100644
--- a/apps/dokploy/server/api/routers/preview-deployment.ts
+++ b/apps/dokploy/server/api/routers/preview-deployment.ts
@@ -14,7 +14,9 @@ export const previewDeploymentRouter = createTRPCRouter({
.input(apiFindAllByApplication)
.query(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -28,7 +30,10 @@ export const previewDeploymentRouter = createTRPCRouter({
const previewDeployment = await findPreviewDeploymentById(
input.previewDeploymentId,
);
- if (previewDeployment.application.project.adminId !== ctx.user.adminId) {
+ if (
+ previewDeployment.application.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this preview deployment",
@@ -43,7 +48,10 @@ export const previewDeploymentRouter = createTRPCRouter({
const previewDeployment = await findPreviewDeploymentById(
input.previewDeploymentId,
);
- if (previewDeployment.application.project.adminId !== ctx.user.adminId) {
+ if (
+ previewDeployment.application.project.organizationId !==
+ ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this preview deployment",
diff --git a/apps/dokploy/server/api/routers/project.ts b/apps/dokploy/server/api/routers/project.ts
index 9c2608cca..438a3f077 100644
--- a/apps/dokploy/server/api/routers/project.ts
+++ b/apps/dokploy/server/api/routers/project.ts
@@ -15,32 +15,34 @@ import {
redis,
} from "@/server/db/schema";
-import { TRPCError } from "@trpc/server";
-import { and, desc, eq, sql } from "drizzle-orm";
-import type { AnyPgColumn } from "drizzle-orm/pg-core";
-
import {
IS_CLOUD,
addNewProject,
checkProjectAccess,
createProject,
deleteProject,
- findAdminById,
+ findMemberById,
findProjectById,
- findUserByAuthId,
+ findUserById,
updateProjectById,
} from "@dokploy/server";
-
+import { TRPCError } from "@trpc/server";
+import { and, desc, eq, sql } from "drizzle-orm";
+import type { AnyPgColumn } from "drizzle-orm/pg-core";
export const projectRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateProject)
.mutation(async ({ ctx, input }) => {
try {
- if (ctx.user.rol === "user") {
- await checkProjectAccess(ctx.user.authId, "create");
+ if (ctx.user.rol === "member") {
+ await checkProjectAccess(
+ ctx.user.id,
+ "create",
+ ctx.session.activeOrganizationId,
+ );
}
- const admin = await findAdminById(ctx.user.adminId);
+ const admin = await findUserById(ctx.user.ownerId);
if (admin.serversQuantity === 0 && IS_CLOUD) {
throw new TRPCError({
@@ -49,9 +51,16 @@ export const projectRouter = createTRPCRouter({
});
}
- const project = await createProject(input, ctx.user.adminId);
- if (ctx.user.rol === "user") {
- await addNewProject(ctx.user.authId, project.projectId);
+ const project = await createProject(
+ input,
+ ctx.session.activeOrganizationId,
+ );
+ if (ctx.user.rol === "member") {
+ await addNewProject(
+ ctx.user.id,
+ project.projectId,
+ ctx.session.activeOrganizationId,
+ );
}
return project;
@@ -67,15 +76,23 @@ export const projectRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneProject)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- const { accessedServices } = await findUserByAuthId(ctx.user.authId);
+ if (ctx.user.rol === "member") {
+ const { accessedServices } = await findMemberById(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
- await checkProjectAccess(ctx.user.authId, "access", input.projectId);
+ await checkProjectAccess(
+ ctx.user.id,
+ "access",
+ ctx.session.activeOrganizationId,
+ input.projectId,
+ );
const project = await db.query.projects.findFirst({
where: and(
eq(projects.projectId, input.projectId),
- eq(projects.adminId, ctx.user.adminId),
+ eq(projects.organizationId, ctx.session.activeOrganizationId),
),
with: {
compose: {
@@ -115,7 +132,7 @@ export const projectRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
@@ -124,9 +141,11 @@ export const projectRouter = createTRPCRouter({
return project;
}),
all: protectedProcedure.query(async ({ ctx }) => {
- if (ctx.user.rol === "user") {
- const { accessedProjects, accessedServices } = await findUserByAuthId(
- ctx.user.authId,
+ // console.log(ctx.user);
+ if (ctx.user.rol === "member") {
+ const { accessedProjects, accessedServices } = await findMemberById(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
);
if (accessedProjects.length === 0) {
@@ -139,7 +158,7 @@ export const projectRouter = createTRPCRouter({
accessedProjects.map((projectId) => sql`${projectId}`),
sql`, `,
)})`,
- eq(projects.adminId, ctx.user.adminId),
+ eq(projects.organizationId, ctx.session.activeOrganizationId),
),
with: {
applications: {
@@ -193,19 +212,26 @@ export const projectRouter = createTRPCRouter({
},
},
},
- where: eq(projects.adminId, ctx.user.adminId),
+ where: eq(projects.organizationId, ctx.session.activeOrganizationId),
orderBy: desc(projects.createdAt),
});
}),
+
remove: protectedProcedure
.input(apiRemoveProject)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkProjectAccess(ctx.user.authId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkProjectAccess(
+ ctx.user.id,
+ "delete",
+ ctx.session.activeOrganizationId,
+ );
}
const currentProject = await findProjectById(input.projectId);
- if (currentProject.adminId !== ctx.user.adminId) {
+ if (
+ currentProject.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this project",
@@ -223,7 +249,9 @@ export const projectRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const currentProject = await findProjectById(input.projectId);
- if (currentProject.adminId !== ctx.user.adminId) {
+ if (
+ currentProject.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this project",
diff --git a/apps/dokploy/server/api/routers/redirects.ts b/apps/dokploy/server/api/routers/redirects.ts
index bcd7962a6..2d520cc42 100644
--- a/apps/dokploy/server/api/routers/redirects.ts
+++ b/apps/dokploy/server/api/routers/redirects.ts
@@ -18,7 +18,9 @@ export const redirectsRouter = createTRPCRouter({
.input(apiCreateRedirect)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -31,7 +33,9 @@ export const redirectsRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const redirect = await findRedirectById(input.redirectId);
const application = await findApplicationById(redirect.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -44,7 +48,9 @@ export const redirectsRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const redirect = await findRedirectById(input.redirectId);
const application = await findApplicationById(redirect.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -57,7 +63,9 @@ export const redirectsRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const redirect = await findRedirectById(input.redirectId);
const application = await findApplicationById(redirect.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
diff --git a/apps/dokploy/server/api/routers/redis.ts b/apps/dokploy/server/api/routers/redis.ts
index 5883e50b1..a80660bf5 100644
--- a/apps/dokploy/server/api/routers/redis.ts
+++ b/apps/dokploy/server/api/routers/redis.ts
@@ -36,8 +36,13 @@ export const redisRouter = createTRPCRouter({
.input(apiCreateRedis)
.mutation(async ({ input, ctx }) => {
try {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.projectId, "create");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.projectId,
+ ctx.session.activeOrganizationId,
+ "create",
+ );
}
if (IS_CLOUD && !input.serverId) {
@@ -48,15 +53,19 @@ export const redisRouter = createTRPCRouter({
}
const project = await findProjectById(input.projectId);
- if (project.adminId !== ctx.user.adminId) {
+ if (project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this project",
});
}
const newRedis = await createRedis(input);
- if (ctx.user.rol === "user") {
- await addNewService(ctx.user.authId, newRedis.redisId);
+ if (ctx.user.rol === "member") {
+ await addNewService(
+ ctx.user.id,
+ newRedis.redisId,
+ project.organizationId,
+ );
}
await createMount({
@@ -75,12 +84,17 @@ export const redisRouter = createTRPCRouter({
one: protectedProcedure
.input(apiFindOneRedis)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.redisId, "access");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.redisId,
+ ctx.session.activeOrganizationId,
+ "access",
+ );
}
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this Redis",
@@ -93,7 +107,7 @@ export const redisRouter = createTRPCRouter({
.input(apiFindOneRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to start this Redis",
@@ -115,7 +129,7 @@ export const redisRouter = createTRPCRouter({
.input(apiResetRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to reload this Redis",
@@ -145,7 +159,7 @@ export const redisRouter = createTRPCRouter({
.input(apiFindOneRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to stop this Redis",
@@ -166,7 +180,7 @@ export const redisRouter = createTRPCRouter({
.input(apiSaveExternalPortRedis)
.mutation(async ({ input, ctx }) => {
const mongo = await findRedisById(input.redisId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this external port",
@@ -182,7 +196,7 @@ export const redisRouter = createTRPCRouter({
.input(apiDeployRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Redis",
@@ -202,7 +216,7 @@ export const redisRouter = createTRPCRouter({
.input(apiDeployRedis)
.subscription(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to deploy this Redis",
@@ -218,7 +232,7 @@ export const redisRouter = createTRPCRouter({
.input(apiChangeRedisStatus)
.mutation(async ({ input, ctx }) => {
const mongo = await findRedisById(input.redisId);
- if (mongo.project.adminId !== ctx.user.adminId) {
+ if (mongo.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to change this Redis status",
@@ -232,19 +246,23 @@ export const redisRouter = createTRPCRouter({
remove: protectedProcedure
.input(apiFindOneRedis)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- await checkServiceAccess(ctx.user.authId, input.redisId, "delete");
+ if (ctx.user.rol === "member") {
+ await checkServiceAccess(
+ ctx.user.id,
+ input.redisId,
+ ctx.session.activeOrganizationId,
+ "delete",
+ );
}
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this Redis",
});
}
-
const cleanupOperations = [
async () => await removeService(redis?.appName, redis.serverId),
async () => await removeRedisById(input.redisId),
@@ -253,7 +271,7 @@ export const redisRouter = createTRPCRouter({
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_) {}
}
return redis;
@@ -262,7 +280,7 @@ export const redisRouter = createTRPCRouter({
.input(apiSaveEnvironmentVariablesRedis)
.mutation(async ({ input, ctx }) => {
const redis = await findRedisById(input.redisId);
- if (redis.project.adminId !== ctx.user.adminId) {
+ if (redis.project.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to save this environment",
diff --git a/apps/dokploy/server/api/routers/registry.ts b/apps/dokploy/server/api/routers/registry.ts
index f66ed4aea..a9a6be891 100644
--- a/apps/dokploy/server/api/routers/registry.ts
+++ b/apps/dokploy/server/api/routers/registry.ts
@@ -1,34 +1,35 @@
+import { db } from "@/server/db";
import {
apiCreateRegistry,
apiFindOneRegistry,
apiRemoveRegistry,
apiTestRegistry,
apiUpdateRegistry,
+ registry,
} from "@/server/db/schema";
import {
IS_CLOUD,
createRegistry,
execAsync,
execAsyncRemote,
- findAllRegistryByAdminId,
findRegistryById,
removeRegistry,
updateRegistry,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
+import { eq } from "drizzle-orm";
import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
-
export const registryRouter = createTRPCRouter({
create: adminProcedure
.input(apiCreateRegistry)
.mutation(async ({ ctx, input }) => {
- return await createRegistry(input, ctx.user.adminId);
+ return await createRegistry(input, ctx.session.activeOrganizationId);
}),
remove: adminProcedure
.input(apiRemoveRegistry)
.mutation(async ({ ctx, input }) => {
const registry = await findRegistryById(input.registryId);
- if (registry.adminId !== ctx.user.adminId) {
+ if (registry.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to delete this registry",
@@ -41,7 +42,7 @@ export const registryRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const { registryId, ...rest } = input;
const registry = await findRegistryById(registryId);
- if (registry.adminId !== ctx.user.adminId) {
+ if (registry.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to update this registry",
@@ -61,13 +62,16 @@ export const registryRouter = createTRPCRouter({
return true;
}),
all: protectedProcedure.query(async ({ ctx }) => {
- return await findAllRegistryByAdminId(ctx.user.adminId);
+ const registryResponse = await db.query.registry.findMany({
+ where: eq(registry.organizationId, ctx.session.activeOrganizationId),
+ });
+ return registryResponse;
}),
one: adminProcedure
.input(apiFindOneRegistry)
.query(async ({ input, ctx }) => {
const registry = await findRegistryById(input.registryId);
- if (registry.adminId !== ctx.user.adminId) {
+ if (registry.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this registry",
diff --git a/apps/dokploy/server/api/routers/security.ts b/apps/dokploy/server/api/routers/security.ts
index 5318a2939..b8e70bbb0 100644
--- a/apps/dokploy/server/api/routers/security.ts
+++ b/apps/dokploy/server/api/routers/security.ts
@@ -18,7 +18,9 @@ export const securityRouter = createTRPCRouter({
.input(apiCreateSecurity)
.mutation(async ({ input, ctx }) => {
const application = await findApplicationById(input.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -31,7 +33,9 @@ export const securityRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const security = await findSecurityById(input.securityId);
const application = await findApplicationById(security.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -44,7 +48,9 @@ export const securityRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const security = await findSecurityById(input.securityId);
const application = await findApplicationById(security.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
@@ -57,7 +63,9 @@ export const securityRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
const security = await findSecurityById(input.securityId);
const application = await findApplicationById(security.applicationId);
- if (application.project.adminId !== ctx.user.adminId) {
+ if (
+ application.project.organizationId !== ctx.session.activeOrganizationId
+ ) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this application",
diff --git a/apps/dokploy/server/api/routers/server.ts b/apps/dokploy/server/api/routers/server.ts
index 382bb98a5..1a9ebc0ac 100644
--- a/apps/dokploy/server/api/routers/server.ts
+++ b/apps/dokploy/server/api/routers/server.ts
@@ -6,11 +6,13 @@ import {
apiFindOneServer,
apiRemoveServer,
apiUpdateServer,
+ apiUpdateServerMonitoring,
applications,
compose,
mariadb,
mongo,
mysql,
+ organization,
postgres,
redis,
server,
@@ -20,35 +22,40 @@ import {
createServer,
defaultCommand,
deleteServer,
- findAdminById,
findServerById,
- findServersByAdminId,
+ findServersByUserId,
+ findUserById,
getPublicIpWithFallback,
haveActiveServices,
removeDeploymentsByServerId,
serverAudit,
serverSetup,
serverValidate,
+ setupMonitoring,
updateServerById,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import { observable } from "@trpc/server/observable";
import { and, desc, eq, getTableColumns, isNotNull, sql } from "drizzle-orm";
+import { z } from "zod";
export const serverRouter = createTRPCRouter({
create: protectedProcedure
.input(apiCreateServer)
.mutation(async ({ ctx, input }) => {
try {
- const admin = await findAdminById(ctx.user.adminId);
- const servers = await findServersByAdminId(admin.adminId);
- if (IS_CLOUD && servers.length >= admin.serversQuantity) {
+ const user = await findUserById(ctx.user.ownerId);
+ const servers = await findServersByUserId(user.id);
+ if (IS_CLOUD && servers.length >= user.serversQuantity) {
throw new TRPCError({
code: "BAD_REQUEST",
message: "You cannot create more servers",
});
}
- const project = await createServer(input, ctx.user.adminId);
+ const project = await createServer(
+ input,
+ ctx.session.activeOrganizationId,
+ );
return project;
} catch (error) {
throw new TRPCError({
@@ -63,7 +70,7 @@ export const serverRouter = createTRPCRouter({
.input(apiFindOneServer)
.query(async ({ input, ctx }) => {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this server",
@@ -74,7 +81,7 @@ export const serverRouter = createTRPCRouter({
}),
getDefaultCommand: protectedProcedure
.input(apiFindOneServer)
- .query(async ({ input, ctx }) => {
+ .query(async () => {
return defaultCommand();
}),
all: protectedProcedure.query(async ({ ctx }) => {
@@ -91,22 +98,37 @@ export const serverRouter = createTRPCRouter({
.leftJoin(mongo, eq(mongo.serverId, server.serverId))
.leftJoin(mysql, eq(mysql.serverId, server.serverId))
.leftJoin(postgres, eq(postgres.serverId, server.serverId))
- .where(eq(server.adminId, ctx.user.adminId))
+ .where(eq(server.organizationId, ctx.session.activeOrganizationId))
.orderBy(desc(server.createdAt))
.groupBy(server.serverId);
return result;
}),
+ count: protectedProcedure.query(async ({ ctx }) => {
+ const organizations = await db.query.organization.findMany({
+ where: eq(organization.ownerId, ctx.user.id),
+ with: {
+ servers: true,
+ },
+ });
+
+ const servers = organizations.flatMap((org) => org.servers);
+
+ return servers.length ?? 0;
+ }),
withSSHKey: protectedProcedure.query(async ({ ctx }) => {
const result = await db.query.server.findMany({
orderBy: desc(server.createdAt),
where: IS_CLOUD
? and(
isNotNull(server.sshKeyId),
- eq(server.adminId, ctx.user.adminId),
+ eq(server.organizationId, ctx.session.activeOrganizationId),
eq(server.serverStatus, "active"),
)
- : and(isNotNull(server.sshKeyId), eq(server.adminId, ctx.user.adminId)),
+ : and(
+ isNotNull(server.sshKeyId),
+ eq(server.organizationId, ctx.session.activeOrganizationId),
+ ),
});
return result;
}),
@@ -115,7 +137,7 @@ export const serverRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to setup this server",
@@ -140,7 +162,7 @@ export const serverRouter = createTRPCRouter({
.subscription(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to setup this server",
@@ -160,7 +182,7 @@ export const serverRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to validate this server",
@@ -202,7 +224,7 @@ export const serverRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to validate this server",
@@ -247,12 +269,54 @@ export const serverRouter = createTRPCRouter({
});
}
}),
+ setupMonitoring: protectedProcedure
+ .input(apiUpdateServerMonitoring)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const server = await findServerById(input.serverId);
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to setup this server",
+ });
+ }
+
+ await updateServerById(input.serverId, {
+ metricsConfig: {
+ server: {
+ type: "Remote",
+ refreshRate: input.metricsConfig.server.refreshRate,
+ retentionDays: input.metricsConfig.server.retentionDays,
+ port: input.metricsConfig.server.port,
+ token: input.metricsConfig.server.token,
+ urlCallback: input.metricsConfig.server.urlCallback,
+ cronJob: input.metricsConfig.server.cronJob,
+ thresholds: {
+ cpu: input.metricsConfig.server.thresholds.cpu,
+ memory: input.metricsConfig.server.thresholds.memory,
+ },
+ },
+ containers: {
+ refreshRate: input.metricsConfig.containers.refreshRate,
+ services: {
+ include: input.metricsConfig.containers.services.include || [],
+ exclude: input.metricsConfig.containers.services.exclude || [],
+ },
+ },
+ },
+ });
+ const currentServer = await setupMonitoring(input.serverId);
+ return currentServer;
+ } catch (error) {
+ throw error;
+ }
+ }),
remove: protectedProcedure
.input(apiRemoveServer)
.mutation(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to delete this server",
@@ -271,12 +335,9 @@ export const serverRouter = createTRPCRouter({
await deleteServer(input.serverId);
if (IS_CLOUD) {
- const admin = await findAdminById(ctx.user.adminId);
+ const admin = await findUserById(ctx.user.ownerId);
- await updateServersBasedOnQuantity(
- admin.adminId,
- admin.serversQuantity,
- );
+ await updateServersBasedOnQuantity(admin.id, admin.serversQuantity);
}
return currentServer;
@@ -289,7 +350,7 @@ export const serverRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to update this server",
@@ -311,11 +372,69 @@ export const serverRouter = createTRPCRouter({
throw error;
}
}),
- publicIp: protectedProcedure.query(async ({ ctx }) => {
+ publicIp: protectedProcedure.query(async () => {
if (IS_CLOUD) {
return "";
}
const ip = await getPublicIpWithFallback();
return ip;
}),
+ getServerMetrics: protectedProcedure
+ .input(
+ z.object({
+ url: z.string(),
+ token: z.string(),
+ dataPoints: z.string(),
+ }),
+ )
+ .query(async ({ input }) => {
+ try {
+ const url = new URL(input.url);
+ url.searchParams.append("limit", input.dataPoints);
+ const response = await fetch(url.toString(), {
+ headers: {
+ Authorization: `Bearer ${input.token}`,
+ },
+ });
+ if (!response.ok) {
+ throw new Error(
+ `Error ${response.status}: ${response.statusText}. Ensure the container is running and this service is included in the monitoring configuration.`,
+ );
+ }
+
+ const data = await response.json();
+ if (!Array.isArray(data) || data.length === 0) {
+ throw new Error(
+ [
+ "No monitoring data available. This could be because:",
+ "",
+ "1. You don't have setup the monitoring service, you can do in web server section.",
+ "2. If you already have setup the monitoring service, wait a few minutes and refresh the page.",
+ ].join("\n"),
+ );
+ }
+ return data as {
+ cpu: string;
+ cpuModel: string;
+ cpuCores: number;
+ cpuPhysicalCores: number;
+ cpuSpeed: number;
+ os: string;
+ distro: string;
+ kernel: string;
+ arch: string;
+ memUsed: string;
+ memUsedGB: string;
+ memTotal: string;
+ uptime: number;
+ diskUsed: string;
+ totalDisk: string;
+ networkIn: string;
+ networkOut: string;
+ timestamp: string;
+ }[];
+ } catch (error) {
+ throw error;
+ }
+ }),
});
diff --git a/apps/dokploy/server/api/routers/settings.ts b/apps/dokploy/server/api/routers/settings.ts
index 449a22333..fc1255fcf 100644
--- a/apps/dokploy/server/api/routers/settings.ts
+++ b/apps/dokploy/server/api/routers/settings.ts
@@ -22,9 +22,8 @@ import {
cleanUpUnusedVolumes,
execAsync,
execAsyncRemote,
- findAdmin,
- findAdminById,
findServerById,
+ findUserById,
getDokployImage,
getDokployImageTag,
getUpdateData,
@@ -47,10 +46,10 @@ import {
startServiceRemote,
stopService,
stopServiceRemote,
- updateAdmin,
updateLetsEncryptEmail,
updateServerById,
updateServerTraefik,
+ updateUser,
writeConfig,
writeMainConfig,
writeTraefikConfigInPath,
@@ -164,7 +163,7 @@ export const settingsRouter = createTRPCRouter({
if (IS_CLOUD) {
return true;
}
- await updateAdmin(ctx.user.authId, {
+ await updateUser(ctx.user.id, {
sshPrivateKey: input.sshPrivateKey,
});
@@ -176,7 +175,7 @@ export const settingsRouter = createTRPCRouter({
if (IS_CLOUD) {
return true;
}
- const admin = await updateAdmin(ctx.user.authId, {
+ const user = await updateUser(ctx.user.id, {
host: input.host,
...(input.letsEncryptEmail && {
letsEncryptEmail: input.letsEncryptEmail,
@@ -184,25 +183,25 @@ export const settingsRouter = createTRPCRouter({
certificateType: input.certificateType,
});
- if (!admin) {
+ if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
- message: "Admin not found",
+ message: "User not found",
});
}
- updateServerTraefik(admin, input.host);
+ updateServerTraefik(user, input.host);
if (input.letsEncryptEmail) {
updateLetsEncryptEmail(input.letsEncryptEmail);
}
- return admin;
+ return user;
}),
cleanSSHPrivateKey: adminProcedure.mutation(async ({ ctx }) => {
if (IS_CLOUD) {
return true;
}
- await updateAdmin(ctx.user.authId, {
+ await updateUser(ctx.user.id, {
sshPrivateKey: null,
});
return true;
@@ -217,7 +216,7 @@ export const settingsRouter = createTRPCRouter({
const server = await findServerById(input.serverId);
- if (server.adminId !== ctx.user.adminId) {
+ if (server.organizationId !== ctx.session?.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not authorized to access this server",
@@ -246,7 +245,7 @@ export const settingsRouter = createTRPCRouter({
await cleanUpUnusedImages(server.serverId);
await cleanUpDockerBuilder(server.serverId);
await cleanUpSystemPrune(server.serverId);
- await sendDockerCleanupNotifications(server.adminId);
+ await sendDockerCleanupNotifications(server.organizationId);
});
}
} else {
@@ -262,19 +261,11 @@ export const settingsRouter = createTRPCRouter({
}
}
} else if (!IS_CLOUD) {
- const admin = await findAdminById(ctx.user.adminId);
-
- if (admin.adminId !== ctx.user.adminId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not authorized to access this admin",
- });
- }
- const adminUpdated = await updateAdmin(ctx.user.authId, {
+ const userUpdated = await updateUser(ctx.user.id, {
enableDockerCleanup: input.enableDockerCleanup,
});
- if (adminUpdated?.enableDockerCleanup) {
+ if (userUpdated?.enableDockerCleanup) {
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
console.log(
`Docker Cleanup ${new Date().toLocaleString()}] Running...`,
@@ -282,7 +273,9 @@ export const settingsRouter = createTRPCRouter({
await cleanUpUnusedImages();
await cleanUpDockerBuilder();
await cleanUpSystemPrune();
- await sendDockerCleanupNotifications(admin.adminId);
+ await sendDockerCleanupNotifications(
+ ctx.session.activeOrganizationId,
+ );
});
} else {
const currentJob = scheduledJobs["docker-cleanup"];
@@ -345,7 +338,7 @@ export const settingsRouter = createTRPCRouter({
writeConfig("middlewares", input.traefikConfig);
return true;
}),
- getUpdateData: adminProcedure.mutation(async () => {
+ getUpdateData: protectedProcedure.mutation(async () => {
if (IS_CLOUD) {
return DEFAULT_UPDATE_DATA;
}
@@ -373,18 +366,21 @@ export const settingsRouter = createTRPCRouter({
return true;
}),
- getDokployVersion: adminProcedure.query(() => {
+ getDokployVersion: protectedProcedure.query(() => {
return packageInfo.version;
}),
- getReleaseTag: adminProcedure.query(() => {
+ getReleaseTag: protectedProcedure.query(() => {
return getDokployImageTag();
}),
readDirectories: protectedProcedure
.input(apiServerSchema)
.query(async ({ ctx, input }) => {
try {
- if (ctx.user.rol === "user") {
- const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
+ if (ctx.user.rol === "member") {
+ const canAccess = await canAccessToTraefikFiles(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
if (!canAccess) {
throw new TRPCError({ code: "UNAUTHORIZED" });
@@ -401,8 +397,11 @@ export const settingsRouter = createTRPCRouter({
updateTraefikFile: protectedProcedure
.input(apiModifyTraefikConfig)
.mutation(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
+ if (ctx.user.rol === "member") {
+ const canAccess = await canAccessToTraefikFiles(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
if (!canAccess) {
throw new TRPCError({ code: "UNAUTHORIZED" });
@@ -419,8 +418,11 @@ export const settingsRouter = createTRPCRouter({
readTraefikFile: protectedProcedure
.input(apiReadTraefikConfig)
.query(async ({ input, ctx }) => {
- if (ctx.user.rol === "user") {
- const canAccess = await canAccessToTraefikFiles(ctx.user.authId);
+ if (ctx.user.rol === "member") {
+ const canAccess = await canAccessToTraefikFiles(
+ ctx.user.id,
+ ctx.session.activeOrganizationId,
+ );
if (!canAccess) {
throw new TRPCError({ code: "UNAUTHORIZED" });
@@ -428,12 +430,12 @@ export const settingsRouter = createTRPCRouter({
}
return readConfigInPath(input.path, input.serverId);
}),
- getIp: protectedProcedure.query(async () => {
+ getIp: protectedProcedure.query(async ({ ctx }) => {
if (IS_CLOUD) {
return true;
}
- const admin = await findAdmin();
- return admin.serverIp;
+ const user = await findUserById(ctx.user.ownerId);
+ return user.serverIp;
}),
getOpenApiDocument: protectedProcedure.query(
@@ -480,10 +482,28 @@ export const settingsRouter = createTRPCRouter({
openApiDocument.info = {
title: "Dokploy API",
description: "Endpoints for dokploy",
- // TODO: get version from package.json
version: "1.0.0",
};
+ // Add security schemes configuration
+ openApiDocument.components = {
+ ...openApiDocument.components,
+ securitySchemes: {
+ apiKey: {
+ type: "apiKey",
+ in: "header",
+ name: "x-api-key",
+ description: "API key authentication",
+ },
+ },
+ };
+
+ // Apply security globally to all endpoints
+ openApiDocument.security = [
+ {
+ apiKey: [],
+ },
+ ];
return openApiDocument;
},
),
@@ -655,7 +675,7 @@ export const settingsRouter = createTRPCRouter({
return true;
}),
- isCloud: protectedProcedure.query(async () => {
+ isCloud: publicProcedure.query(async () => {
return IS_CLOUD;
}),
health: publicProcedure.query(async () => {
@@ -715,7 +735,12 @@ export const settingsRouter = createTRPCRouter({
try {
return await checkGPUStatus(input.serverId || "");
} catch (error) {
- throw new Error("Failed to check GPU status");
+ const message =
+ error instanceof Error ? error.message : "Failed to check GPU status";
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message,
+ });
}
}),
updateTraefikPorts: adminProcedure
diff --git a/apps/dokploy/server/api/routers/ssh-key.ts b/apps/dokploy/server/api/routers/ssh-key.ts
index fe2f24f91..4663af8f2 100644
--- a/apps/dokploy/server/api/routers/ssh-key.ts
+++ b/apps/dokploy/server/api/routers/ssh-key.ts
@@ -9,7 +9,6 @@ import {
sshKeys,
} from "@/server/db/schema";
import {
- IS_CLOUD,
createSshKey,
findSSHKeyById,
generateSSHKey,
@@ -26,7 +25,7 @@ export const sshRouter = createTRPCRouter({
try {
await createSshKey({
...input,
- adminId: ctx.user.adminId,
+ organizationId: ctx.session.activeOrganizationId,
});
} catch (error) {
throw new TRPCError({
@@ -41,8 +40,7 @@ export const sshRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const sshKey = await findSSHKeyById(input.sshKeyId);
- if (IS_CLOUD && sshKey.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (sshKey.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to delete this SSH key",
@@ -59,8 +57,7 @@ export const sshRouter = createTRPCRouter({
.query(async ({ input, ctx }) => {
const sshKey = await findSSHKeyById(input.sshKeyId);
- if (IS_CLOUD && sshKey.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (sshKey.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to access this SSH key",
@@ -70,10 +67,9 @@ export const sshRouter = createTRPCRouter({
}),
all: protectedProcedure.query(async ({ ctx }) => {
return await db.query.sshKeys.findMany({
- ...(IS_CLOUD && { where: eq(sshKeys.adminId, ctx.user.adminId) }),
+ where: eq(sshKeys.organizationId, ctx.session.activeOrganizationId),
orderBy: desc(sshKeys.createdAt),
});
- // TODO: Remove this line when the cloud version is ready
}),
generate: protectedProcedure
.input(apiGenerateSSHKey)
@@ -85,8 +81,7 @@ export const sshRouter = createTRPCRouter({
.mutation(async ({ input, ctx }) => {
try {
const sshKey = await findSSHKeyById(input.sshKeyId);
- if (IS_CLOUD && sshKey.adminId !== ctx.user.adminId) {
- // TODO: Remove isCloud in the next versions of dokploy
+ if (sshKey.organizationId !== ctx.session.activeOrganizationId) {
throw new TRPCError({
code: "UNAUTHORIZED",
message: "You are not allowed to update this SSH key",
diff --git a/apps/dokploy/server/api/routers/stripe.ts b/apps/dokploy/server/api/routers/stripe.ts
index 7a8a537c1..a226eeac8 100644
--- a/apps/dokploy/server/api/routers/stripe.ts
+++ b/apps/dokploy/server/api/routers/stripe.ts
@@ -1,9 +1,9 @@
import { WEBSITE_URL, getStripeItems } from "@/server/utils/stripe";
import {
IS_CLOUD,
- findAdminById,
- findServersByAdminId,
- updateAdmin,
+ findServersByUserId,
+ findUserById,
+ updateUser,
} from "@dokploy/server";
import { TRPCError } from "@trpc/server";
import Stripe from "stripe";
@@ -12,8 +12,8 @@ import { adminProcedure, createTRPCRouter } from "../trpc";
export const stripeRouter = createTRPCRouter({
getProducts: adminProcedure.query(async ({ ctx }) => {
- const admin = await findAdminById(ctx.user.adminId);
- const stripeCustomerId = admin.stripeCustomerId;
+ const user = await findUserById(ctx.user.ownerId);
+ const stripeCustomerId = user.stripeCustomerId;
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
apiVersion: "2024-09-30.acacia",
@@ -56,15 +56,15 @@ export const stripeRouter = createTRPCRouter({
});
const items = getStripeItems(input.serverQuantity, input.isAnnual);
- const admin = await findAdminById(ctx.user.adminId);
+ const user = await findUserById(ctx.user.id);
- let stripeCustomerId = admin.stripeCustomerId;
+ let stripeCustomerId = user.stripeCustomerId;
if (stripeCustomerId) {
const customer = await stripe.customers.retrieve(stripeCustomerId);
if (customer.deleted) {
- await updateAdmin(admin.authId, {
+ await updateUser(user.id, {
stripeCustomerId: null,
});
stripeCustomerId = null;
@@ -78,7 +78,7 @@ export const stripeRouter = createTRPCRouter({
customer: stripeCustomerId,
}),
metadata: {
- adminId: admin.adminId,
+ adminId: user.id,
},
allow_promotion_codes: true,
success_url: `${WEBSITE_URL}/dashboard/settings/servers?success=true`,
@@ -87,45 +87,43 @@ export const stripeRouter = createTRPCRouter({
return { sessionId: session.id };
}),
- createCustomerPortalSession: adminProcedure.mutation(
- async ({ ctx, input }) => {
- const admin = await findAdminById(ctx.user.adminId);
+ createCustomerPortalSession: adminProcedure.mutation(async ({ ctx }) => {
+ const user = await findUserById(ctx.user.id);
- if (!admin.stripeCustomerId) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Stripe Customer ID not found",
- });
- }
- const stripeCustomerId = admin.stripeCustomerId;
+ if (!user.stripeCustomerId) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Stripe Customer ID not found",
+ });
+ }
+ const stripeCustomerId = user.stripeCustomerId;
- const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
- apiVersion: "2024-09-30.acacia",
+ const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
+ apiVersion: "2024-09-30.acacia",
+ });
+
+ try {
+ const session = await stripe.billingPortal.sessions.create({
+ customer: stripeCustomerId,
+ return_url: `${WEBSITE_URL}/dashboard/settings/billing`,
});
- try {
- const session = await stripe.billingPortal.sessions.create({
- customer: stripeCustomerId,
- return_url: `${WEBSITE_URL}/dashboard/settings/billing`,
- });
-
- return { url: session.url };
- } catch (error) {
- return {
- url: "",
- };
- }
- },
- ),
+ return { url: session.url };
+ } catch (_) {
+ return {
+ url: "",
+ };
+ }
+ }),
canCreateMoreServers: adminProcedure.query(async ({ ctx }) => {
- const admin = await findAdminById(ctx.user.adminId);
- const servers = await findServersByAdminId(admin.adminId);
+ const user = await findUserById(ctx.user.ownerId);
+ const servers = await findServersByUserId(user.id);
if (!IS_CLOUD) {
return true;
}
- return servers.length < admin.serversQuantity;
+ return servers.length < user.serversQuantity;
}),
});
diff --git a/apps/dokploy/server/api/routers/user.ts b/apps/dokploy/server/api/routers/user.ts
index 91db98261..0b740ab74 100644
--- a/apps/dokploy/server/api/routers/user.ts
+++ b/apps/dokploy/server/api/routers/user.ts
@@ -1,34 +1,330 @@
-import { apiFindOneUser, apiFindOneUserByAuth } from "@/server/db/schema";
-import { findUserByAuthId, findUserById, findUsers } from "@dokploy/server";
+import {
+ IS_CLOUD,
+ findOrganizationById,
+ findUserById,
+ getUserByToken,
+ removeUserById,
+ updateUser,
+ createApiKey,
+} from "@dokploy/server";
+import { db } from "@dokploy/server/db";
+import {
+ account,
+ apiAssignPermissions,
+ apiFindOneToken,
+ apiUpdateUser,
+ invitation,
+ member,
+ apikey,
+} from "@dokploy/server/db/schema";
+import * as bcrypt from "bcrypt";
import { TRPCError } from "@trpc/server";
-import { adminProcedure, createTRPCRouter, protectedProcedure } from "../trpc";
+import { and, asc, eq, gt } from "drizzle-orm";
+import { z } from "zod";
+import {
+ adminProcedure,
+ createTRPCRouter,
+ protectedProcedure,
+ publicProcedure,
+} from "../trpc";
+
+const apiCreateApiKey = z.object({
+ name: z.string().min(1),
+ prefix: z.string().optional(),
+ expiresIn: z.number().optional(),
+ metadata: z.object({
+ organizationId: z.string(),
+ }),
+ // Rate limiting
+ rateLimitEnabled: z.boolean().optional(),
+ rateLimitTimeWindow: z.number().optional(),
+ rateLimitMax: z.number().optional(),
+ // Request limiting
+ remaining: z.number().optional(),
+ refillAmount: z.number().optional(),
+ refillInterval: z.number().optional(),
+});
export const userRouter = createTRPCRouter({
all: adminProcedure.query(async ({ ctx }) => {
- return await findUsers(ctx.user.adminId);
+ return await db.query.member.findMany({
+ where: eq(member.organizationId, ctx.session.activeOrganizationId),
+ with: {
+ user: true,
+ },
+ orderBy: [asc(member.createdAt)],
+ });
}),
- byAuthId: protectedProcedure
- .input(apiFindOneUserByAuth)
+ one: protectedProcedure
+ .input(
+ z.object({
+ userId: z.string(),
+ }),
+ )
.query(async ({ input, ctx }) => {
- const user = await findUserByAuthId(input.authId);
- if (user.adminId !== ctx.user.adminId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not allowed to access this user",
- });
- }
- return user;
+ const memberResult = await db.query.member.findFirst({
+ where: and(
+ eq(member.userId, input.userId),
+ eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
+ ),
+ with: {
+ user: true,
+ },
+ });
+
+ return memberResult;
}),
- byUserId: protectedProcedure
- .input(apiFindOneUser)
- .query(async ({ input, ctx }) => {
- const user = await findUserById(input.userId);
- if (user.adminId !== ctx.user.adminId) {
- throw new TRPCError({
- code: "UNAUTHORIZED",
- message: "You are not allowed to access this user",
+ get: protectedProcedure.query(async ({ ctx }) => {
+ const memberResult = await db.query.member.findFirst({
+ where: and(
+ eq(member.userId, ctx.user.id),
+ eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
+ ),
+ with: {
+ user: {
+ with: {
+ apiKeys: true,
+ },
+ },
+ },
+ });
+
+ return memberResult;
+ }),
+ getServerMetrics: protectedProcedure.query(async ({ ctx }) => {
+ const memberResult = await db.query.member.findFirst({
+ where: and(
+ eq(member.userId, ctx.user.id),
+ eq(member.organizationId, ctx.session?.activeOrganizationId || ""),
+ ),
+ with: {
+ user: true,
+ },
+ });
+
+ return memberResult?.user;
+ }),
+ update: protectedProcedure
+ .input(apiUpdateUser)
+ .mutation(async ({ input, ctx }) => {
+ if (input.password || input.currentPassword) {
+ const currentAuth = await db.query.account.findFirst({
+ where: eq(account.userId, ctx.user.id),
});
+ const correctPassword = bcrypt.compareSync(
+ input.currentPassword || "",
+ currentAuth?.password || "",
+ );
+
+ if (!correctPassword) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "Current password is incorrect",
+ });
+ }
+
+ if (!input.password) {
+ throw new TRPCError({
+ code: "BAD_REQUEST",
+ message: "New password is required",
+ });
+ }
+ await db
+ .update(account)
+ .set({
+ password: bcrypt.hashSync(input.password, 10),
+ })
+ .where(eq(account.userId, ctx.user.id));
}
- return user;
+ return await updateUser(ctx.user.id, input);
+ }),
+ getUserByToken: publicProcedure
+ .input(apiFindOneToken)
+ .query(async ({ input }) => {
+ return await getUserByToken(input.token);
+ }),
+ getMetricsToken: protectedProcedure.query(async ({ ctx }) => {
+ const user = await findUserById(ctx.user.ownerId);
+ return {
+ serverIp: user.serverIp,
+ enabledFeatures: user.enablePaidFeatures,
+ metricsConfig: user?.metricsConfig,
+ };
+ }),
+ remove: protectedProcedure
+ .input(
+ z.object({
+ userId: z.string(),
+ }),
+ )
+ .mutation(async ({ input }) => {
+ if (IS_CLOUD) {
+ return true;
+ }
+ return await removeUserById(input.userId);
+ }),
+ assignPermissions: adminProcedure
+ .input(apiAssignPermissions)
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const organization = await findOrganizationById(
+ ctx.session?.activeOrganizationId || "",
+ );
+
+ if (organization?.ownerId !== ctx.user.ownerId) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not allowed to assign permissions",
+ });
+ }
+
+ const { id, ...rest } = input;
+
+ await db
+ .update(member)
+ .set({
+ ...rest,
+ })
+ .where(
+ and(
+ eq(member.userId, input.id),
+ eq(
+ member.organizationId,
+ ctx.session?.activeOrganizationId || "",
+ ),
+ ),
+ );
+ } catch (error) {
+ throw error;
+ }
+ }),
+ getInvitations: protectedProcedure.query(async ({ ctx }) => {
+ return await db.query.invitation.findMany({
+ where: and(
+ eq(invitation.email, ctx.user.email),
+ gt(invitation.expiresAt, new Date()),
+ eq(invitation.status, "pending"),
+ ),
+ with: {
+ organization: true,
+ },
+ });
+ }),
+
+ getContainerMetrics: protectedProcedure
+ .input(
+ z.object({
+ url: z.string(),
+ token: z.string(),
+ appName: z.string(),
+ dataPoints: z.string(),
+ }),
+ )
+ .query(async ({ input }) => {
+ try {
+ if (!input.appName) {
+ throw new Error(
+ [
+ "No Application Selected:",
+ "",
+ "Make Sure to select an application to monitor.",
+ ].join("\n"),
+ );
+ }
+ const url = new URL(`${input.url}/metrics/containers`);
+ url.searchParams.append("limit", input.dataPoints);
+ url.searchParams.append("appName", input.appName);
+ const response = await fetch(url.toString(), {
+ headers: {
+ Authorization: `Bearer ${input.token}`,
+ },
+ });
+ if (!response.ok) {
+ throw new Error(
+ `Error ${response.status}: ${response.statusText}. Please verify that the application "${input.appName}" is running and this service is included in the monitoring configuration.`,
+ );
+ }
+
+ const data = await response.json();
+ if (!Array.isArray(data) || data.length === 0) {
+ throw new Error(
+ [
+ `No monitoring data available for "${input.appName}". This could be because:`,
+ "",
+ "1. The container was recently started - wait a few minutes for data to be collected",
+ "2. The container is not running - verify its status",
+ "3. The service is not included in your monitoring configuration",
+ ].join("\n"),
+ );
+ }
+ return data as {
+ containerId: string;
+ containerName: string;
+ containerImage: string;
+ containerLabels: string;
+ containerCommand: string;
+ containerCreated: string;
+ }[];
+ } catch (error) {
+ throw error;
+ }
+ }),
+
+ generateToken: protectedProcedure.mutation(async () => {
+ return "token";
+ }),
+
+ deleteApiKey: protectedProcedure
+ .input(
+ z.object({
+ apiKeyId: z.string(),
+ }),
+ )
+ .mutation(async ({ input, ctx }) => {
+ try {
+ const apiKeyToDelete = await db.query.apikey.findFirst({
+ where: eq(apikey.id, input.apiKeyId),
+ });
+
+ if (!apiKeyToDelete) {
+ throw new TRPCError({
+ code: "NOT_FOUND",
+ message: "API key not found",
+ });
+ }
+
+ if (apiKeyToDelete.userId !== ctx.user.id) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "You are not authorized to delete this API key",
+ });
+ }
+
+ await db.delete(apikey).where(eq(apikey.id, input.apiKeyId));
+ return true;
+ } catch (error) {
+ throw error;
+ }
+ }),
+
+ createApiKey: protectedProcedure
+ .input(apiCreateApiKey)
+ .mutation(async ({ input, ctx }) => {
+ const apiKey = await createApiKey(ctx.user.id, input);
+ return apiKey;
+ }),
+
+ checkUserOrganizations: protectedProcedure
+ .input(
+ z.object({
+ userId: z.string(),
+ }),
+ )
+ .query(async ({ input }) => {
+ const organizations = await db.query.member.findMany({
+ where: eq(member.userId, input.userId),
+ });
+
+ return organizations.length;
}),
});
diff --git a/apps/dokploy/server/api/trpc.ts b/apps/dokploy/server/api/trpc.ts
index db4f7adfe..4c88eb22d 100644
--- a/apps/dokploy/server/api/trpc.ts
+++ b/apps/dokploy/server/api/trpc.ts
@@ -9,7 +9,7 @@
// import { getServerAuthSession } from "@/server/auth";
import { db } from "@/server/db";
-import { validateBearerToken, validateRequest } from "@dokploy/server";
+import { validateRequest } from "@dokploy/server/lib/auth";
import type { OpenApiMeta } from "@dokploy/trpc-openapi";
import { TRPCError, initTRPC } from "@trpc/server";
import type { CreateNextContextOptions } from "@trpc/server/adapters/next";
@@ -18,7 +18,7 @@ import {
experimental_isMultipartFormDataRequest,
experimental_parseMultipartFormData,
} from "@trpc/server/adapters/node-http/content-type/form-data";
-import type { Session, User } from "lucia";
+import type { Session, User } from "better-auth";
import superjson from "superjson";
import { ZodError } from "zod";
/**
@@ -30,8 +30,8 @@ import { ZodError } from "zod";
*/
interface CreateContextOptions {
- user: (User & { authId: string; adminId: string }) | null;
- session: Session | null;
+ user: (User & { rol: "member" | "admin" | "owner"; ownerId: string }) | null;
+ session: (Session & { activeOrganizationId: string }) | null;
req: CreateNextContextOptions["req"];
res: CreateNextContextOptions["res"];
}
@@ -65,30 +65,29 @@ const createInnerTRPCContext = (opts: CreateContextOptions) => {
export const createTRPCContext = async (opts: CreateNextContextOptions) => {
const { req, res } = opts;
- let { session, user } = await validateBearerToken(req);
-
- if (!session) {
- const cookieResult = await validateRequest(req, res);
- session = cookieResult.session;
- user = cookieResult.user;
- }
+ // Get from the request
+ const { session, user } = await validateRequest(req);
return createInnerTRPCContext({
req,
res,
- session: session,
- ...((user && {
- user: {
- authId: user.id,
- email: user.email,
- rol: user.rol,
- id: user.id,
- secret: user.secret,
- adminId: user.adminId,
- },
- }) || {
- user: null,
- }),
+ // @ts-ignore
+ session: session
+ ? {
+ ...session,
+ activeOrganizationId: session.activeOrganizationId || "",
+ }
+ : null,
+ // @ts-ignore
+ user: user
+ ? {
+ ...user,
+ email: user.email,
+ rol: user.role as "owner" | "member" | "admin",
+ id: user.id,
+ ownerId: user.ownerId,
+ }
+ : null,
});
};
@@ -181,7 +180,7 @@ export const uploadProcedure = async (opts: any) => {
};
export const cliProcedure = t.procedure.use(({ ctx, next }) => {
- if (!ctx.session || !ctx.user || ctx.user.rol !== "admin") {
+ if (!ctx.session || !ctx.user || ctx.user.rol !== "owner") {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
@@ -195,7 +194,7 @@ export const cliProcedure = t.procedure.use(({ ctx, next }) => {
});
export const adminProcedure = t.procedure.use(({ ctx, next }) => {
- if (!ctx.session || !ctx.user || ctx.user.rol !== "admin") {
+ if (!ctx.session || !ctx.user || ctx.user.rol !== "owner") {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({
diff --git a/apps/dokploy/server/db/seed.ts b/apps/dokploy/server/db/seed.ts
index b79350797..5b3eb6c62 100644
--- a/apps/dokploy/server/db/seed.ts
+++ b/apps/dokploy/server/db/seed.ts
@@ -1,16 +1,10 @@
-import bc from "bcrypt";
import { drizzle } from "drizzle-orm/postgres-js";
import postgres from "postgres";
-import { users } from "./schema";
const connectionString = process.env.DATABASE_URL!;
const pg = postgres(connectionString, { max: 1 });
-const db = drizzle(pg);
-
-function password(txt: string) {
- return bc.hashSync(txt, 10);
-}
+const _db = drizzle(pg);
async function seed() {
console.log("> Seed:", process.env.DATABASE_PATH, "\n");
diff --git a/apps/dokploy/server/utils/backup.ts b/apps/dokploy/server/utils/backup.ts
index a178063fa..4fc9db931 100644
--- a/apps/dokploy/server/utils/backup.ts
+++ b/apps/dokploy/server/utils/backup.ts
@@ -1,3 +1,9 @@
+import {
+ type BackupScheduleList,
+ IS_CLOUD,
+ removeScheduleBackup,
+} from "@dokploy/server/index";
+
type QueueJob =
| {
type: "backup";
@@ -59,3 +65,19 @@ export const updateJob = async (job: QueueJob) => {
throw error;
}
};
+
+export const cancelJobs = async (backups: BackupScheduleList) => {
+ for (const backup of backups) {
+ if (backup.enabled) {
+ if (IS_CLOUD) {
+ await removeJob({
+ cronSchedule: backup.schedule,
+ backupId: backup.backupId,
+ type: "backup",
+ });
+ } else {
+ removeScheduleBackup(backup.backupId);
+ }
+ }
+ }
+};
diff --git a/apps/dokploy/server/utils/docker.ts b/apps/dokploy/server/utils/docker.ts
index 92008678f..3314eb62e 100644
--- a/apps/dokploy/server/utils/docker.ts
+++ b/apps/dokploy/server/utils/docker.ts
@@ -6,7 +6,7 @@ export const isWSL = async () => {
const { stdout } = await execAsync("uname -r");
const isWSL = stdout.includes("microsoft");
return isWSL;
- } catch (error) {
+ } catch (_error) {
return false;
}
};
diff --git a/apps/dokploy/server/wss/docker-container-logs.ts b/apps/dokploy/server/wss/docker-container-logs.ts
index 092f39735..8d08ebd46 100644
--- a/apps/dokploy/server/wss/docker-container-logs.ts
+++ b/apps/dokploy/server/wss/docker-container-logs.ts
@@ -1,5 +1,5 @@
import type http from "node:http";
-import { findServerById, validateWebSocketRequest } from "@dokploy/server";
+import { findServerById, validateRequest } from "@dokploy/server";
import { spawn } from "node-pty";
import { Client } from "ssh2";
import { WebSocketServer } from "ws";
@@ -35,7 +35,7 @@ export const setupDockerContainerLogsWebSocketServer = (
const since = url.searchParams.get("since");
const serverId = url.searchParams.get("serverId");
const runType = url.searchParams.get("runType");
- const { user, session } = await validateWebSocketRequest(req);
+ const { user, session } = await validateRequest(req);
if (!containerId) {
ws.close(4000, "containerId no provided");
diff --git a/apps/dokploy/server/wss/docker-container-terminal.ts b/apps/dokploy/server/wss/docker-container-terminal.ts
index 8981ccbc2..2f25edb1a 100644
--- a/apps/dokploy/server/wss/docker-container-terminal.ts
+++ b/apps/dokploy/server/wss/docker-container-terminal.ts
@@ -1,5 +1,5 @@
import type http from "node:http";
-import { findServerById, validateWebSocketRequest } from "@dokploy/server";
+import { findServerById, validateRequest } from "@dokploy/server";
import { spawn } from "node-pty";
import { Client } from "ssh2";
import { WebSocketServer } from "ws";
@@ -32,7 +32,7 @@ export const setupDockerContainerTerminalWebSocketServer = (
const containerId = url.searchParams.get("containerId");
const activeWay = url.searchParams.get("activeWay");
const serverId = url.searchParams.get("serverId");
- const { user, session } = await validateWebSocketRequest(req);
+ const { user, session } = await validateRequest(req);
if (!containerId) {
ws.close(4000, "containerId no provided");
@@ -50,8 +50,8 @@ export const setupDockerContainerTerminalWebSocketServer = (
throw new Error("No SSH key available for this server");
const conn = new Client();
- let stdout = "";
- let stderr = "";
+ let _stdout = "";
+ let _stderr = "";
conn
.once("ready", () => {
conn.exec(
@@ -61,16 +61,16 @@ export const setupDockerContainerTerminalWebSocketServer = (
if (err) throw err;
stream
- .on("close", (code: number, signal: string) => {
+ .on("close", (code: number, _signal: string) => {
ws.send(`\nContainer closed with code: ${code}\n`);
conn.end();
})
.on("data", (data: string) => {
- stdout += data.toString();
+ _stdout += data.toString();
ws.send(data.toString());
})
.stderr.on("data", (data) => {
- stderr += data.toString();
+ _stderr += data.toString();
ws.send(data.toString());
console.error("Error: ", data.toString());
});
diff --git a/apps/dokploy/server/wss/docker-stats.ts b/apps/dokploy/server/wss/docker-stats.ts
index 89d94687b..99e993dce 100644
--- a/apps/dokploy/server/wss/docker-stats.ts
+++ b/apps/dokploy/server/wss/docker-stats.ts
@@ -1,9 +1,10 @@
import type http from "node:http";
import {
docker,
+ execAsync,
getLastAdvancedStatsFile,
recordAdvancedStats,
- validateWebSocketRequest,
+ validateRequest,
} from "@dokploy/server";
import { WebSocketServer } from "ws";
@@ -35,7 +36,7 @@ export const setupDockerStatsMonitoringSocketServer = (
| "application"
| "stack"
| "docker-compose";
- const { user, session } = await validateWebSocketRequest(req);
+ const { user, session } = await validateRequest(req);
if (!appName) {
ws.close(4000, "appName no provided");
@@ -70,12 +71,16 @@ export const setupDockerStatsMonitoringSocketServer = (
ws.close(4000, "Container not running");
return;
}
+ const { stdout, stderr } = await execAsync(
+ `docker stats ${container.Id} --no-stream --format \'{"BlockIO":"{{.BlockIO}}","CPUPerc":"{{.CPUPerc}}","Container":"{{.Container}}","ID":"{{.ID}}","MemPerc":"{{.MemPerc}}","MemUsage":"{{.MemUsage}}","Name":"{{.Name}}","NetIO":"{{.NetIO}}"}\'`,
+ );
+ if (stderr) {
+ console.error("Docker stats error:", stderr);
+ return;
+ }
+ const stat = JSON.parse(stdout);
- const stats = await docker.getContainer(container.Id).stats({
- stream: false,
- });
-
- await recordAdvancedStats(stats, appName);
+ await recordAdvancedStats(stat, appName);
const data = await getLastAdvancedStatsFile(appName);
ws.send(
diff --git a/apps/dokploy/server/wss/drawer-logs.ts b/apps/dokploy/server/wss/drawer-logs.ts
index c1dec315b..404dfeee5 100644
--- a/apps/dokploy/server/wss/drawer-logs.ts
+++ b/apps/dokploy/server/wss/drawer-logs.ts
@@ -1,4 +1,5 @@
import type http from "node:http";
+import { validateRequest } from "@dokploy/server/index";
import { applyWSSHandler } from "@trpc/server/adapters/ws";
import { WebSocketServer } from "ws";
import { appRouter } from "../api/root";
@@ -31,6 +32,12 @@ export const setupDrawerLogsWebSocketServer = (
});
wssTerm.on("connection", async (ws, req) => {
- const url = new URL(req.url || "", `http://${req.headers.host}`);
+ const _url = new URL(req.url || "", `http://${req.headers.host}`);
+ const { user, session } = await validateRequest(req);
+
+ if (!user || !session) {
+ ws.close();
+ return;
+ }
});
};
diff --git a/apps/dokploy/server/wss/listen-deployment.ts b/apps/dokploy/server/wss/listen-deployment.ts
index df77ceb41..4a25c6f0e 100644
--- a/apps/dokploy/server/wss/listen-deployment.ts
+++ b/apps/dokploy/server/wss/listen-deployment.ts
@@ -1,6 +1,6 @@
import { spawn } from "node:child_process";
import type http from "node:http";
-import { findServerById, validateWebSocketRequest } from "@dokploy/server";
+import { findServerById, validateRequest } from "@dokploy/server";
import { Client } from "ssh2";
import { WebSocketServer } from "ws";
@@ -29,7 +29,7 @@ export const setupDeploymentLogsWebSocketServer = (
const url = new URL(req.url || "", `http://${req.headers.host}`);
const logPath = url.searchParams.get("logPath");
const serverId = url.searchParams.get("serverId");
- const { user, session } = await validateWebSocketRequest(req);
+ const { user, session } = await validateRequest(req);
if (!logPath) {
console.log("logPath no provided");
@@ -103,7 +103,7 @@ export const setupDeploymentLogsWebSocketServer = (
ws.close();
});
}
- } catch (error) {
+ } catch (_error) {
// @ts-ignore
// const errorMessage = error?.message as unknown as string;
// ws.send(errorMessage);
diff --git a/apps/dokploy/server/wss/terminal.ts b/apps/dokploy/server/wss/terminal.ts
index 5fa1accc2..094c5e157 100644
--- a/apps/dokploy/server/wss/terminal.ts
+++ b/apps/dokploy/server/wss/terminal.ts
@@ -1,9 +1,5 @@
import type http from "node:http";
-import {
- IS_CLOUD,
- findServerById,
- validateWebSocketRequest,
-} from "@dokploy/server";
+import { IS_CLOUD, findServerById, validateRequest } from "@dokploy/server";
import { publicIpv4, publicIpv6 } from "public-ip";
import { Client, type ConnectConfig } from "ssh2";
import { WebSocketServer } from "ws";
@@ -71,7 +67,7 @@ export const setupTerminalWebSocketServer = (
wssTerm.on("connection", async (ws, req) => {
const url = new URL(req.url || "", `http://${req.headers.host}`);
const serverId = url.searchParams.get("serverId");
- const { user, session } = await validateWebSocketRequest(req);
+ const { user, session } = await validateRequest(req);
if (!user || !session || !serverId) {
ws.close();
return;
@@ -148,8 +144,8 @@ export const setupTerminalWebSocketServer = (
}
const conn = new Client();
- let stdout = "";
- let stderr = "";
+ let _stdout = "";
+ let _stderr = "";
ws.send("Connecting...\n");
@@ -162,16 +158,16 @@ export const setupTerminalWebSocketServer = (
if (err) throw err;
stream
- .on("close", (code: number, signal: string) => {
+ .on("close", (code: number, _signal: string) => {
ws.send(`\nContainer closed with code: ${code}\n`);
conn.end();
})
.on("data", (data: string) => {
- stdout += data.toString();
+ _stdout += data.toString();
ws.send(data.toString());
})
.stderr.on("data", (data) => {
- stderr += data.toString();
+ _stderr += data.toString();
ws.send(data.toString());
console.error("Error: ", data.toString());
});
diff --git a/apps/dokploy/styles/globals.css b/apps/dokploy/styles/globals.css
index 7b7977b9c..74a1d2764 100644
--- a/apps/dokploy/styles/globals.css
+++ b/apps/dokploy/styles/globals.css
@@ -4,6 +4,7 @@
@layer base {
:root {
+ --terminal-paste: rgba(0, 0, 0, 0.2);
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
@@ -51,6 +52,7 @@
}
.dark {
+ --terminal-paste: rgba(255, 255, 255, 0.2);
--background: 0 0% 0%;
--foreground: 0 0% 98%;
@@ -235,3 +237,8 @@
background-color: hsl(var(--muted-foreground) / 0.5);
}
}
+
+.xterm-bg-257.xterm-fg-257 {
+ background-color: var(--terminal-paste) !important;
+ color: currentColor !important;
+}
diff --git a/apps/dokploy/templates/activepieces/docker-compose.yml b/apps/dokploy/templates/activepieces/docker-compose.yml
index e990379bc..a5511e7fa 100644
--- a/apps/dokploy/templates/activepieces/docker-compose.yml
+++ b/apps/dokploy/templates/activepieces/docker-compose.yml
@@ -4,8 +4,7 @@ services:
activepieces:
image: activepieces/activepieces:0.35.0
restart: unless-stopped
- networks:
- - dokploy-network
+
depends_on:
postgres:
condition: service_healthy
@@ -35,8 +34,7 @@ services:
postgres:
image: postgres:14
restart: unless-stopped
- networks:
- - dokploy-network
+
environment:
POSTGRES_DB: activepieces
POSTGRES_PASSWORD: ${AP_POSTGRES_PASSWORD}
@@ -52,8 +50,7 @@ services:
redis:
image: redis:7
restart: unless-stopped
- networks:
- - dokploy-network
+
volumes:
- redis_data:/data
healthcheck:
diff --git a/apps/dokploy/templates/alist/docker-compose.yml b/apps/dokploy/templates/alist/docker-compose.yml
new file mode 100644
index 000000000..9ff67c943
--- /dev/null
+++ b/apps/dokploy/templates/alist/docker-compose.yml
@@ -0,0 +1,14 @@
+version: '3.3'
+services:
+ alist:
+ image: xhofe/alist:v3.41.0
+ volumes:
+ - alist-data:/opt/alist/data
+ environment:
+ - PUID=0
+ - PGID=0
+ - UMASK=022
+ restart: unless-stopped
+
+volumes:
+ alist-data:
\ No newline at end of file
diff --git a/apps/dokploy/templates/alist/index.ts b/apps/dokploy/templates/alist/index.ts
new file mode 100644
index 000000000..2a27f5708
--- /dev/null
+++ b/apps/dokploy/templates/alist/index.ts
@@ -0,0 +1,22 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 5244,
+ serviceName: "alist",
+ },
+ ];
+
+ return {
+ domains,
+ };
+}
diff --git a/apps/dokploy/templates/answer/docker-compose.yml b/apps/dokploy/templates/answer/docker-compose.yml
new file mode 100644
index 000000000..2b9fc3440
--- /dev/null
+++ b/apps/dokploy/templates/answer/docker-compose.yml
@@ -0,0 +1,30 @@
+services:
+ answer:
+ image: apache/answer:1.4.1
+ ports:
+ - '80'
+ restart: on-failure
+ volumes:
+ - answer-data:/data
+ depends_on:
+ db:
+ condition: service_healthy
+ db:
+ image: postgres:16
+ restart: always
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+ volumes:
+ - db-data:/var/lib/postgresql/data
+ environment:
+ POSTGRES_DB: answer
+ POSTGRES_USER: postgres
+ POSTGRES_PASSWORD: postgres
+
+volumes:
+ answer-data:
+ db-data:
diff --git a/apps/dokploy/templates/answer/index.ts b/apps/dokploy/templates/answer/index.ts
new file mode 100644
index 000000000..36d48cb36
--- /dev/null
+++ b/apps/dokploy/templates/answer/index.ts
@@ -0,0 +1,33 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateHash,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainServiceHash = generateHash(schema.projectName);
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 9080,
+ serviceName: "answer",
+ },
+ ];
+
+ const envs = [
+ `ANSWER_HOST=http://${mainDomain}`,
+ `SERVICE_HASH=${mainServiceHash}`,
+ ];
+
+ const mounts: Template["mounts"] = [];
+
+ return {
+ envs,
+ mounts,
+ domains,
+ };
+}
diff --git a/apps/dokploy/templates/appsmith/index.ts b/apps/dokploy/templates/appsmith/index.ts
index ff744a249..73279e91b 100644
--- a/apps/dokploy/templates/appsmith/index.ts
+++ b/apps/dokploy/templates/appsmith/index.ts
@@ -7,7 +7,7 @@ import {
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
+ const _mainServiceHash = generateHash(schema.projectName);
const domains: DomainSchema[] = [
{
diff --git a/apps/dokploy/templates/appwrite/docker-compose.yml b/apps/dokploy/templates/appwrite/docker-compose.yml
new file mode 100644
index 000000000..163cb3d03
--- /dev/null
+++ b/apps/dokploy/templates/appwrite/docker-compose.yml
@@ -0,0 +1,887 @@
+version: "3.8"
+
+x-logging: &x-logging
+ logging:
+ driver: "json-file"
+ options:
+ max-file: "5"
+ max-size: "10m"
+
+services:
+ appwrite:
+ image: appwrite/appwrite:1.6.0
+ container_name: appwrite
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=appwrite
+ volumes:
+ - appwrite-uploads:/storage/uploads:rw
+ - appwrite-cache:/storage/cache:rw
+ - appwrite-config:/storage/config:rw
+ - appwrite-certificates:/storage/certificates:rw
+ - appwrite-functions:/storage/functions:rw
+ depends_on:
+ - mariadb
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_LOCALE
+ - _APP_CONSOLE_WHITELIST_ROOT
+ - _APP_CONSOLE_WHITELIST_EMAILS
+ - _APP_CONSOLE_SESSION_ALERTS
+ - _APP_CONSOLE_WHITELIST_IPS
+ - _APP_CONSOLE_HOSTNAMES
+ - _APP_SYSTEM_EMAIL_NAME
+ - _APP_SYSTEM_EMAIL_ADDRESS
+ - _APP_EMAIL_SECURITY
+ - _APP_SYSTEM_RESPONSE_FORMAT
+ - _APP_OPTIONS_ABUSE
+ - _APP_OPTIONS_ROUTER_PROTECTION
+ - _APP_OPTIONS_FORCE_HTTPS
+ - _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
+ - _APP_DOMAIN_FUNCTIONS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_SMTP_HOST
+ - _APP_SMTP_PORT
+ - _APP_SMTP_SECURE
+ - _APP_SMTP_USERNAME
+ - _APP_SMTP_PASSWORD
+ - _APP_USAGE_STATS
+ - _APP_STORAGE_LIMIT
+ - _APP_STORAGE_PREVIEW_LIMIT
+ - _APP_STORAGE_ANTIVIRUS
+ - _APP_STORAGE_ANTIVIRUS_HOST
+ - _APP_STORAGE_ANTIVIRUS_PORT
+ - _APP_STORAGE_DEVICE
+ - _APP_STORAGE_S3_ACCESS_KEY
+ - _APP_STORAGE_S3_SECRET
+ - _APP_STORAGE_S3_REGION
+ - _APP_STORAGE_S3_BUCKET
+ - _APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - _APP_STORAGE_DO_SPACES_SECRET
+ - _APP_STORAGE_DO_SPACES_REGION
+ - _APP_STORAGE_DO_SPACES_BUCKET
+ - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - _APP_STORAGE_BACKBLAZE_SECRET
+ - _APP_STORAGE_BACKBLAZE_REGION
+ - _APP_STORAGE_BACKBLAZE_BUCKET
+ - _APP_STORAGE_LINODE_ACCESS_KEY
+ - _APP_STORAGE_LINODE_SECRET
+ - _APP_STORAGE_LINODE_REGION
+ - _APP_STORAGE_LINODE_BUCKET
+ - _APP_STORAGE_WASABI_ACCESS_KEY
+ - _APP_STORAGE_WASABI_SECRET
+ - _APP_STORAGE_WASABI_REGION
+ - _APP_STORAGE_WASABI_BUCKET
+ - _APP_FUNCTIONS_SIZE_LIMIT
+ - _APP_FUNCTIONS_TIMEOUT
+ - _APP_FUNCTIONS_BUILD_TIMEOUT
+ - _APP_FUNCTIONS_CPUS
+ - _APP_FUNCTIONS_MEMORY
+ - _APP_FUNCTIONS_RUNTIMES
+ - _APP_EXECUTOR_SECRET
+ - _APP_EXECUTOR_HOST
+ - _APP_LOGGING_CONFIG
+ - _APP_MAINTENANCE_INTERVAL
+ - _APP_MAINTENANCE_DELAY
+ - _APP_MAINTENANCE_RETENTION_EXECUTION
+ - _APP_MAINTENANCE_RETENTION_CACHE
+ - _APP_MAINTENANCE_RETENTION_ABUSE
+ - _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
+ - _APP_MAINTENANCE_RETENTION_SCHEDULES
+ - _APP_SMS_PROVIDER
+ - _APP_SMS_FROM
+ - _APP_GRAPHQL_MAX_BATCH_SIZE
+ - _APP_GRAPHQL_MAX_COMPLEXITY
+ - _APP_GRAPHQL_MAX_DEPTH
+ - _APP_VCS_GITHUB_APP_NAME
+ - _APP_VCS_GITHUB_PRIVATE_KEY
+ - _APP_VCS_GITHUB_APP_ID
+ - _APP_VCS_GITHUB_WEBHOOK_SECRET
+ - _APP_VCS_GITHUB_CLIENT_SECRET
+ - _APP_VCS_GITHUB_CLIENT_ID
+ - _APP_MIGRATIONS_FIREBASE_CLIENT_ID
+ - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
+ - _APP_ASSISTANT_OPENAI_API_KEY
+
+ appwrite-console:
+ image: appwrite/console:5.0.12
+ container_name: appwrite-console
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ labels:
+ - "traefik.enable=true"
+ - "traefik.constraint-label-stack=appwrite"
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_LOCALE
+ - _APP_CONSOLE_WHITELIST_ROOT
+ - _APP_CONSOLE_WHITELIST_EMAILS
+ - _APP_CONSOLE_SESSION_ALERTS
+ - _APP_CONSOLE_WHITELIST_IPS
+ - _APP_CONSOLE_HOSTNAMES
+ - _APP_SYSTEM_EMAIL_NAME
+ - _APP_SYSTEM_EMAIL_ADDRESS
+ - _APP_EMAIL_SECURITY
+ - _APP_SYSTEM_RESPONSE_FORMAT
+ - _APP_OPTIONS_ABUSE
+ - _APP_OPTIONS_ROUTER_PROTECTION
+ - _APP_OPTIONS_FORCE_HTTPS
+ - _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
+ - _APP_DOMAIN_FUNCTIONS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_SMTP_HOST
+ - _APP_SMTP_PORT
+ - _APP_SMTP_SECURE
+ - _APP_SMTP_USERNAME
+ - _APP_SMTP_PASSWORD
+ - _APP_USAGE_STATS
+ - _APP_STORAGE_LIMIT
+ - _APP_STORAGE_PREVIEW_LIMIT
+ - _APP_STORAGE_ANTIVIRUS
+ - _APP_STORAGE_ANTIVIRUS_HOST
+ - _APP_STORAGE_ANTIVIRUS_PORT
+ - _APP_STORAGE_DEVICE
+ - _APP_STORAGE_S3_ACCESS_KEY
+ - _APP_STORAGE_S3_SECRET
+ - _APP_STORAGE_S3_REGION
+ - _APP_STORAGE_S3_BUCKET
+ - _APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - _APP_STORAGE_DO_SPACES_SECRET
+ - _APP_STORAGE_DO_SPACES_REGION
+ - _APP_STORAGE_DO_SPACES_BUCKET
+ - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - _APP_STORAGE_BACKBLAZE_SECRET
+ - _APP_STORAGE_BACKBLAZE_REGION
+ - _APP_STORAGE_BACKBLAZE_BUCKET
+ - _APP_STORAGE_LINODE_ACCESS_KEY
+ - _APP_STORAGE_LINODE_SECRET
+ - _APP_STORAGE_LINODE_REGION
+ - _APP_STORAGE_LINODE_BUCKET
+ - _APP_STORAGE_WASABI_ACCESS_KEY
+ - _APP_STORAGE_WASABI_SECRET
+ - _APP_STORAGE_WASABI_REGION
+ - _APP_STORAGE_WASABI_BUCKET
+
+ appwrite-realtime:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: realtime
+ container_name: appwrite-realtime
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - mariadb
+ - redis
+ labels:
+ - "traefik.enable=true"
+ - "traefik.constraint-label-stack=appwrite"
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPTIONS_ABUSE
+ - _APP_OPTIONS_ROUTER_PROTECTION
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_USAGE_STATS
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-audits:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-audits
+ <<: *x-logging
+ container_name: appwrite-worker-audits
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-webhooks:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-webhooks
+ <<: *x-logging
+ container_name: appwrite-worker-webhooks
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_EMAIL_SECURITY
+ - _APP_SYSTEM_SECURITY_EMAIL_ADDRESS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-deletes:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-deletes
+ <<: *x-logging
+ container_name: appwrite-worker-deletes
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ volumes:
+ - appwrite-uploads:/storage/uploads:rw
+ - appwrite-cache:/storage/cache:rw
+ - appwrite-functions:/storage/functions:rw
+ - appwrite-builds:/storage/builds:rw
+ - appwrite-certificates:/storage/certificates:rw
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_STORAGE_DEVICE
+ - _APP_STORAGE_S3_ACCESS_KEY
+ - _APP_STORAGE_S3_SECRET
+ - _APP_STORAGE_S3_REGION
+ - _APP_STORAGE_S3_BUCKET
+ - _APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - _APP_STORAGE_DO_SPACES_SECRET
+ - _APP_STORAGE_DO_SPACES_REGION
+ - _APP_STORAGE_DO_SPACES_BUCKET
+ - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - _APP_STORAGE_BACKBLAZE_SECRET
+ - _APP_STORAGE_BACKBLAZE_REGION
+ - _APP_STORAGE_BACKBLAZE_BUCKET
+ - _APP_STORAGE_LINODE_ACCESS_KEY
+ - _APP_STORAGE_LINODE_SECRET
+ - _APP_STORAGE_LINODE_REGION
+ - _APP_STORAGE_LINODE_BUCKET
+ - _APP_STORAGE_WASABI_ACCESS_KEY
+ - _APP_STORAGE_WASABI_SECRET
+ - _APP_STORAGE_WASABI_REGION
+ - _APP_STORAGE_WASABI_BUCKET
+ - _APP_LOGGING_CONFIG
+ - _APP_EXECUTOR_SECRET
+ - _APP_EXECUTOR_HOST
+ - _APP_MAINTENANCE_RETENTION_ABUSE
+ - _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_EXECUTION
+
+ appwrite-worker-databases:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-databases
+ <<: *x-logging
+ container_name: appwrite-worker-databases
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-builds:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-builds
+ <<: *x-logging
+ container_name: appwrite-worker-builds
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ volumes:
+ - appwrite-functions:/storage/functions:rw
+ - appwrite-builds:/storage/builds:rw
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_EXECUTOR_SECRET
+ - _APP_EXECUTOR_HOST
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+ - _APP_VCS_GITHUB_APP_NAME
+ - _APP_VCS_GITHUB_PRIVATE_KEY
+ - _APP_VCS_GITHUB_APP_ID
+ - _APP_FUNCTIONS_TIMEOUT
+ - _APP_FUNCTIONS_BUILD_TIMEOUT
+ - _APP_FUNCTIONS_CPUS
+ - _APP_FUNCTIONS_MEMORY
+ - _APP_FUNCTIONS_SIZE_LIMIT
+ - _APP_OPTIONS_FORCE_HTTPS
+ - _APP_OPTIONS_FUNCTIONS_FORCE_HTTPS
+ - _APP_DOMAIN
+ - _APP_STORAGE_DEVICE
+ - _APP_STORAGE_S3_ACCESS_KEY
+ - _APP_STORAGE_S3_SECRET
+ - _APP_STORAGE_S3_REGION
+ - _APP_STORAGE_S3_BUCKET
+ - _APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - _APP_STORAGE_DO_SPACES_SECRET
+ - _APP_STORAGE_DO_SPACES_REGION
+ - _APP_STORAGE_DO_SPACES_BUCKET
+ - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - _APP_STORAGE_BACKBLAZE_SECRET
+ - _APP_STORAGE_BACKBLAZE_REGION
+ - _APP_STORAGE_BACKBLAZE_BUCKET
+ - _APP_STORAGE_LINODE_ACCESS_KEY
+ - _APP_STORAGE_LINODE_SECRET
+ - _APP_STORAGE_LINODE_REGION
+ - _APP_STORAGE_LINODE_BUCKET
+ - _APP_STORAGE_WASABI_ACCESS_KEY
+ - _APP_STORAGE_WASABI_SECRET
+ - _APP_STORAGE_WASABI_REGION
+ - _APP_STORAGE_WASABI_BUCKET
+
+ appwrite-worker-certificates:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-certificates
+ <<: *x-logging
+ container_name: appwrite-worker-certificates
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ volumes:
+ - appwrite-config:/storage/config:rw
+ - appwrite-certificates:/storage/certificates:rw
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
+ - _APP_DOMAIN_FUNCTIONS
+ - _APP_EMAIL_CERTIFICATES
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-functions:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-functions
+ <<: *x-logging
+ container_name: appwrite-worker-functions
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ - openruntimes-executor
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_OPTIONS_FORCE_HTTPS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_FUNCTIONS_TIMEOUT
+ - _APP_FUNCTIONS_BUILD_TIMEOUT
+ - _APP_FUNCTIONS_CPUS
+ - _APP_FUNCTIONS_MEMORY
+ - _APP_EXECUTOR_SECRET
+ - _APP_EXECUTOR_HOST
+ - _APP_USAGE_STATS
+ - _APP_DOCKER_HUB_USERNAME
+ - _APP_DOCKER_HUB_PASSWORD
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-mails:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-mails
+ <<: *x-logging
+ container_name: appwrite-worker-mails
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_SYSTEM_EMAIL_NAME
+ - _APP_SYSTEM_EMAIL_ADDRESS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_SMTP_HOST
+ - _APP_SMTP_PORT
+ - _APP_SMTP_SECURE
+ - _APP_SMTP_USERNAME
+ - _APP_SMTP_PASSWORD
+ - _APP_LOGGING_CONFIG
+
+ appwrite-worker-messaging:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-messaging
+ container_name: appwrite-worker-messaging
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ volumes:
+ - appwrite-uploads:/storage/uploads:rw
+ depends_on:
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+ - _APP_SMS_FROM
+ - _APP_SMS_PROVIDER
+ - _APP_STORAGE_DEVICE
+ - _APP_STORAGE_S3_ACCESS_KEY
+ - _APP_STORAGE_S3_SECRET
+ - _APP_STORAGE_S3_REGION
+ - _APP_STORAGE_S3_BUCKET
+ - _APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - _APP_STORAGE_DO_SPACES_SECRET
+ - _APP_STORAGE_DO_SPACES_REGION
+ - _APP_STORAGE_DO_SPACES_BUCKET
+ - _APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - _APP_STORAGE_BACKBLAZE_SECRET
+ - _APP_STORAGE_BACKBLAZE_REGION
+ - _APP_STORAGE_BACKBLAZE_BUCKET
+ - _APP_STORAGE_LINODE_ACCESS_KEY
+ - _APP_STORAGE_LINODE_SECRET
+ - _APP_STORAGE_LINODE_REGION
+ - _APP_STORAGE_LINODE_BUCKET
+ - _APP_STORAGE_WASABI_ACCESS_KEY
+ - _APP_STORAGE_WASABI_SECRET
+ - _APP_STORAGE_WASABI_REGION
+ - _APP_STORAGE_WASABI_BUCKET
+
+ appwrite-worker-migrations:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-migrations
+ <<: *x-logging
+ container_name: appwrite-worker-migrations
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
+ - _APP_EMAIL_SECURITY
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_LOGGING_CONFIG
+ - _APP_MIGRATIONS_FIREBASE_CLIENT_ID
+ - _APP_MIGRATIONS_FIREBASE_CLIENT_SECRET
+
+ appwrite-task-maintenance:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: maintenance
+ <<: *x-logging
+ container_name: appwrite-task-maintenance
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_DOMAIN
+ - _APP_DOMAIN_TARGET
+ - _APP_DOMAIN_FUNCTIONS
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_MAINTENANCE_INTERVAL
+ - _APP_MAINTENANCE_RETENTION_EXECUTION
+ - _APP_MAINTENANCE_RETENTION_CACHE
+ - _APP_MAINTENANCE_RETENTION_ABUSE
+ - _APP_MAINTENANCE_RETENTION_AUDIT
+ - _APP_MAINTENANCE_RETENTION_USAGE_HOURLY
+ - _APP_MAINTENANCE_RETENTION_SCHEDULES
+
+ appwrite-worker-usage:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-usage
+ container_name: appwrite-worker-usage
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_USAGE_STATS
+ - _APP_LOGGING_CONFIG
+ - _APP_USAGE_AGGREGATION_INTERVAL
+
+ appwrite-worker-usage-dump:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: worker-usage-dump
+ container_name: appwrite-worker-usage-dump
+ <<: *x-logging
+ networks:
+ - dokploy-network
+ depends_on:
+ - redis
+ - mariadb
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_USAGE_STATS
+ - _APP_LOGGING_CONFIG
+ - _APP_USAGE_AGGREGATION_INTERVAL
+
+ appwrite-task-scheduler-functions:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: schedule-functions
+ container_name: appwrite-task-scheduler-functions
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - mariadb
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+
+ appwrite-task-scheduler-executions:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: schedule-executions
+ container_name: appwrite-task-scheduler-executions
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - mariadb
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+
+ appwrite-task-scheduler-messages:
+ image: appwrite/appwrite:1.6.0
+ entrypoint: schedule-messages
+ container_name: appwrite-task-scheduler-messages
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ depends_on:
+ - mariadb
+ - redis
+ environment:
+ - _APP_ENV
+ - _APP_WORKER_PER_CORE
+ - _APP_OPENSSL_KEY_V1
+ - _APP_REDIS_HOST
+ - _APP_REDIS_PORT
+ - _APP_REDIS_USER
+ - _APP_REDIS_PASS
+ - _APP_DB_HOST
+ - _APP_DB_PORT
+ - _APP_DB_SCHEMA
+ - _APP_DB_USER
+ - _APP_DB_PASS
+
+ appwrite-assistant:
+ image: appwrite/assistant:0.4.0
+ container_name: appwrite-assistant
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ environment:
+ - _APP_ASSISTANT_OPENAI_API_KEY
+
+ openruntimes-executor:
+ container_name: openruntimes-executor
+ hostname: exc1
+ <<: *x-logging
+ restart: unless-stopped
+ stop_signal: SIGINT
+ image: openruntimes/executor:0.6.11
+ networks:
+ - dokploy-network
+ volumes:
+ - /var/run/docker.sock:/var/run/docker.sock
+ - appwrite-builds:/storage/builds:rw
+ - appwrite-functions:/storage/functions:rw
+ - /tmp:/tmp:rw
+ environment:
+ - OPR_EXECUTOR_INACTIVE_TRESHOLD=$_APP_FUNCTIONS_INACTIVE_THRESHOLD
+ - OPR_EXECUTOR_MAINTENANCE_INTERVAL=$_APP_FUNCTIONS_MAINTENANCE_INTERVAL
+ - OPR_EXECUTOR_NETWORK=$_APP_FUNCTIONS_RUNTIMES_NETWORK
+ - OPR_EXECUTOR_DOCKER_HUB_USERNAME=$_APP_DOCKER_HUB_USERNAME
+ - OPR_EXECUTOR_DOCKER_HUB_PASSWORD=$_APP_DOCKER_HUB_PASSWORD
+ - OPR_EXECUTOR_ENV=$_APP_ENV
+ - OPR_EXECUTOR_RUNTIMES=$_APP_FUNCTIONS_RUNTIMES
+ - OPR_EXECUTOR_SECRET=$_APP_EXECUTOR_SECRET
+ - OPR_EXECUTOR_LOGGING_CONFIG=$_APP_LOGGING_CONFIG
+ - OPR_EXECUTOR_STORAGE_DEVICE=$_APP_STORAGE_DEVICE
+ - OPR_EXECUTOR_STORAGE_S3_ACCESS_KEY=$_APP_STORAGE_S3_ACCESS_KEY
+ - OPR_EXECUTOR_STORAGE_S3_SECRET=$_APP_STORAGE_S3_SECRET
+ - OPR_EXECUTOR_STORAGE_S3_REGION=$_APP_STORAGE_S3_REGION
+ - OPR_EXECUTOR_STORAGE_S3_BUCKET=$_APP_STORAGE_S3_BUCKET
+ - OPR_EXECUTOR_STORAGE_DO_SPACES_ACCESS_KEY=$_APP_STORAGE_DO_SPACES_ACCESS_KEY
+ - OPR_EXECUTOR_STORAGE_DO_SPACES_SECRET=$_APP_STORAGE_DO_SPACES_SECRET
+ - OPR_EXECUTOR_STORAGE_DO_SPACES_REGION=$_APP_STORAGE_DO_SPACES_REGION
+ - OPR_EXECUTOR_STORAGE_DO_SPACES_BUCKET=$_APP_STORAGE_DO_SPACES_BUCKET
+ - OPR_EXECUTOR_STORAGE_BACKBLAZE_ACCESS_KEY=$_APP_STORAGE_BACKBLAZE_ACCESS_KEY
+ - OPR_EXECUTOR_STORAGE_BACKBLAZE_SECRET=$_APP_STORAGE_BACKBLAZE_SECRET
+ - OPR_EXECUTOR_STORAGE_BACKBLAZE_REGION=$_APP_STORAGE_BACKBLAZE_REGION
+ - OPR_EXECUTOR_STORAGE_BACKBLAZE_BUCKET=$_APP_STORAGE_BACKBLAZE_BUCKET
+ - OPR_EXECUTOR_STORAGE_LINODE_ACCESS_KEY=$_APP_STORAGE_LINODE_ACCESS_KEY
+ - OPR_EXECUTOR_STORAGE_LINODE_SECRET=$_APP_STORAGE_LINODE_SECRET
+ - OPR_EXECUTOR_STORAGE_LINODE_REGION=$_APP_STORAGE_LINODE_REGION
+ - OPR_EXECUTOR_STORAGE_LINODE_BUCKET=$_APP_STORAGE_LINODE_BUCKET
+ - OPR_EXECUTOR_STORAGE_WASABI_ACCESS_KEY=$_APP_STORAGE_WASABI_ACCESS_KEY
+ - OPR_EXECUTOR_STORAGE_WASABI_SECRET=$_APP_STORAGE_WASABI_SECRET
+ - OPR_EXECUTOR_STORAGE_WASABI_REGION=$_APP_STORAGE_WASABI_REGION
+ - OPR_EXECUTOR_STORAGE_WASABI_BUCKET=$_APP_STORAGE_WASABI_BUCKET
+
+ mariadb:
+ image: mariadb:10.11
+ container_name: appwrite-mariadb
+ <<: *x-logging
+ restart: unless-stopped
+ networks:
+ - dokploy-network
+ volumes:
+ - appwrite-mariadb:/var/lib/mysql:rw
+ environment:
+ - MYSQL_ROOT_PASSWORD=${_APP_DB_ROOT_PASS}
+ - MYSQL_DATABASE=${_APP_DB_SCHEMA}
+ - MYSQL_USER=${_APP_DB_USER}
+ - MYSQL_PASSWORD=${_APP_DB_PASS}
+ - MARIADB_AUTO_UPGRADE=1
+ command: "mysqld --innodb-flush-method=fsync"
+
+ redis:
+ image: redis:7.2.4-alpine
+ container_name: appwrite-redis
+ <<: *x-logging
+ restart: unless-stopped
+ command: >
+ redis-server
+ --maxmemory 512mb
+ --maxmemory-policy allkeys-lru
+ --maxmemory-samples 5
+ networks:
+ - dokploy-network
+ volumes:
+ - appwrite-redis:/data:rw
+
+# Uncomment and configure if ClamAV is needed
+# clamav:
+# image: appwrite/clamav:1.2.0
+# container_name: appwrite-clamav
+# restart: unless-stopped
+# networks:
+# - dokploy-network
+# volumes:
+# - appwrite-uploads:/storage/uploads
+
+volumes:
+ appwrite-mariadb:
+ appwrite-redis:
+ appwrite-cache:
+ appwrite-uploads:
+ appwrite-certificates:
+ appwrite-functions:
+ appwrite-builds:
+ appwrite-config:
+
+networks:
+ dokploy-network:
+ external: true
diff --git a/apps/dokploy/templates/appwrite/index.ts b/apps/dokploy/templates/appwrite/index.ts
new file mode 100644
index 000000000..4e671324f
--- /dev/null
+++ b/apps/dokploy/templates/appwrite/index.ts
@@ -0,0 +1,153 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ { host: mainDomain, port: 80, serviceName: "appwrite", path: "/" },
+ {
+ host: mainDomain,
+ port: 80,
+ serviceName: "appwrite-console",
+ path: "/console",
+ },
+ {
+ host: mainDomain,
+ port: 80,
+ serviceName: "appwrite-realtime",
+ path: "/v1/realtime",
+ },
+ ];
+
+ const envs = [
+ "_APP_ENV=production",
+ "_APP_LOCALE=en",
+ "_APP_OPTIONS_ABUSE=enabled",
+ "_APP_OPTIONS_FORCE_HTTPS=disabled",
+ "_APP_OPTIONS_FUNCTIONS_FORCE_HTTPS=disabled",
+ "_APP_OPTIONS_ROUTER_PROTECTION=disabled",
+ "_APP_OPENSSL_KEY_V1=your-secret-key",
+ `_APP_DOMAIN=${mainDomain}`,
+ `_APP_DOMAIN_FUNCTIONS=${mainDomain}`,
+ `_APP_DOMAIN_TARGET=${mainDomain}`,
+ "_APP_CONSOLE_WHITELIST_ROOT=enabled",
+ "_APP_CONSOLE_WHITELIST_EMAILS=",
+ "_APP_CONSOLE_WHITELIST_IPS=",
+ "_APP_CONSOLE_HOSTNAMES=",
+ "_APP_SYSTEM_EMAIL_NAME=Appwrite",
+ "_APP_SYSTEM_EMAIL_ADDRESS=noreply@appwrite.io",
+ "_APP_SYSTEM_TEAM_EMAIL=team@appwrite.io",
+ "_APP_SYSTEM_RESPONSE_FORMAT=",
+ "_APP_SYSTEM_SECURITY_EMAIL_ADDRESS=certs@appwrite.io",
+ "_APP_EMAIL_SECURITY=",
+ "_APP_EMAIL_CERTIFICATES=",
+ "_APP_USAGE_STATS=enabled",
+ "_APP_LOGGING_PROVIDER=",
+ "_APP_LOGGING_CONFIG=",
+ "_APP_USAGE_AGGREGATION_INTERVAL=30",
+ "_APP_USAGE_TIMESERIES_INTERVAL=30",
+ "_APP_USAGE_DATABASE_INTERVAL=900",
+ "_APP_WORKER_PER_CORE=6",
+ "_APP_CONSOLE_SESSION_ALERTS=disabled",
+ "_APP_REDIS_HOST=redis",
+ "_APP_REDIS_PORT=6379",
+ "_APP_REDIS_USER=",
+ "_APP_REDIS_PASS=",
+ "_APP_DB_HOST=mariadb",
+ "_APP_DB_PORT=3306",
+ "_APP_DB_SCHEMA=appwrite",
+ "_APP_DB_USER=user",
+ "_APP_DB_PASS=password",
+ "_APP_DB_ROOT_PASS=rootsecretpassword",
+ "_APP_INFLUXDB_HOST=influxdb",
+ "_APP_INFLUXDB_PORT=8086",
+ "_APP_STATSD_HOST=telegraf",
+ "_APP_STATSD_PORT=8125",
+ "_APP_SMTP_HOST=",
+ "_APP_SMTP_PORT=",
+ "_APP_SMTP_SECURE=",
+ "_APP_SMTP_USERNAME=",
+ "_APP_SMTP_PASSWORD=",
+ "_APP_SMS_PROVIDER=",
+ "_APP_SMS_FROM=",
+ "_APP_STORAGE_LIMIT=30000000",
+ "_APP_STORAGE_PREVIEW_LIMIT=20000000",
+ "_APP_STORAGE_ANTIVIRUS=disabled",
+ "_APP_STORAGE_ANTIVIRUS_HOST=clamav",
+ "_APP_STORAGE_ANTIVIRUS_PORT=3310",
+ "_APP_STORAGE_DEVICE=local",
+ "_APP_STORAGE_S3_ACCESS_KEY=",
+ "_APP_STORAGE_S3_SECRET=",
+ "_APP_STORAGE_S3_REGION=us-east-1",
+ "_APP_STORAGE_S3_BUCKET=",
+ "_APP_STORAGE_DO_SPACES_ACCESS_KEY=",
+ "_APP_STORAGE_DO_SPACES_SECRET=",
+ "_APP_STORAGE_DO_SPACES_REGION=us-east-1",
+ "_APP_STORAGE_DO_SPACES_BUCKET=",
+ "_APP_STORAGE_BACKBLAZE_ACCESS_KEY=",
+ "_APP_STORAGE_BACKBLAZE_SECRET=",
+ "_APP_STORAGE_BACKBLAZE_REGION=us-west-004",
+ "_APP_STORAGE_BACKBLAZE_BUCKET=",
+ "_APP_STORAGE_LINODE_ACCESS_KEY=",
+ "_APP_STORAGE_LINODE_SECRET=",
+ "_APP_STORAGE_LINODE_REGION=eu-central-1",
+ "_APP_STORAGE_LINODE_BUCKET=",
+ "_APP_STORAGE_WASABI_ACCESS_KEY=",
+ "_APP_STORAGE_WASABI_SECRET=",
+ "_APP_STORAGE_WASABI_REGION=eu-central-1",
+ "_APP_STORAGE_WASABI_BUCKET=",
+ "_APP_FUNCTIONS_SIZE_LIMIT=30000000",
+ "_APP_FUNCTIONS_BUILD_SIZE_LIMIT=2000000000",
+ "_APP_FUNCTIONS_TIMEOUT=900",
+ "_APP_FUNCTIONS_BUILD_TIMEOUT=900",
+ "_APP_FUNCTIONS_CONTAINERS=10",
+ "_APP_FUNCTIONS_CPUS=0",
+ "_APP_FUNCTIONS_MEMORY=0",
+ "_APP_FUNCTIONS_MEMORY_SWAP=0",
+ "_APP_FUNCTIONS_RUNTIMES=node-16.0,php-8.0,python-3.9,ruby-3.0",
+ "_APP_EXECUTOR_SECRET=your-secret-key",
+ "_APP_EXECUTOR_HOST=http://exc1/v1",
+ "_APP_EXECUTOR_RUNTIME_NETWORK=appwrite_runtimes",
+ "_APP_FUNCTIONS_ENVS=node-16.0,php-7.4,python-3.9,ruby-3.0",
+ "_APP_FUNCTIONS_INACTIVE_THRESHOLD=60",
+ "DOCKERHUB_PULL_USERNAME=",
+ "DOCKERHUB_PULL_PASSWORD=",
+ "DOCKERHUB_PULL_EMAIL=",
+ "OPEN_RUNTIMES_NETWORK=appwrite_runtimes",
+ "_APP_FUNCTIONS_RUNTIMES_NETWORK=runtimes",
+ "_APP_DOCKER_HUB_USERNAME=",
+ "_APP_DOCKER_HUB_PASSWORD=",
+ "_APP_FUNCTIONS_MAINTENANCE_INTERVAL=3600",
+ "_APP_VCS_GITHUB_APP_NAME=",
+ "_APP_VCS_GITHUB_PRIVATE_KEY=",
+ "_APP_VCS_GITHUB_APP_ID=",
+ "_APP_VCS_GITHUB_CLIENT_ID=",
+ "_APP_VCS_GITHUB_CLIENT_SECRET=",
+ "_APP_VCS_GITHUB_WEBHOOK_SECRET=",
+ "_APP_MAINTENANCE_INTERVAL=86400",
+ "_APP_MAINTENANCE_DELAY=0",
+ "_APP_MAINTENANCE_RETENTION_CACHE=2592000",
+ "_APP_MAINTENANCE_RETENTION_EXECUTION=1209600",
+ "_APP_MAINTENANCE_RETENTION_AUDIT=1209600",
+ "_APP_MAINTENANCE_RETENTION_ABUSE=86400",
+ "_APP_MAINTENANCE_RETENTION_USAGE_HOURLY=8640000",
+ "_APP_MAINTENANCE_RETENTION_SCHEDULES=86400",
+ "_APP_GRAPHQL_MAX_BATCH_SIZE=10",
+ "_APP_GRAPHQL_MAX_COMPLEXITY=250",
+ "_APP_GRAPHQL_MAX_DEPTH=3",
+ "_APP_MIGRATIONS_FIREBASE_CLIENT_ID=",
+ "_APP_MIGRATIONS_FIREBASE_CLIENT_SECRET=",
+ "_APP_ASSISTANT_OPENAI_API_KEY=",
+ ];
+
+ return {
+ domains,
+ envs,
+ mounts: [],
+ };
+}
diff --git a/apps/dokploy/templates/aptabase/docker-compose.yml b/apps/dokploy/templates/aptabase/docker-compose.yml
index 934fd1eea..dfde1caef 100644
--- a/apps/dokploy/templates/aptabase/docker-compose.yml
+++ b/apps/dokploy/templates/aptabase/docker-compose.yml
@@ -7,8 +7,7 @@ services:
environment:
POSTGRES_USER: aptabase
POSTGRES_PASSWORD: sTr0NGp4ssw0rd
- networks:
- - dokploy-network
+
healthcheck:
test: ["CMD-SHELL", "pg_isready -U aptabase"]
interval: 10s
@@ -27,8 +26,7 @@ services:
nofile:
soft: 262144
hard: 262144
- networks:
- - dokploy-network
+
healthcheck:
test: ["CMD-SHELL", "curl -f http://localhost:8123 || exit 1"]
interval: 10s
diff --git a/apps/dokploy/templates/blender/index.ts b/apps/dokploy/templates/blender/index.ts
index 84e527554..79508bed5 100644
--- a/apps/dokploy/templates/blender/index.ts
+++ b/apps/dokploy/templates/blender/index.ts
@@ -7,7 +7,7 @@ import {
} from "../utils";
export function generate(schema: Schema): Template {
- const mainServiceHash = generateHash(schema.projectName);
+ const _mainServiceHash = generateHash(schema.projectName);
const mainDomain = generateRandomDomain(schema);
const domains: DomainSchema[] = [
diff --git a/apps/dokploy/templates/budibase/docker-compose.yml b/apps/dokploy/templates/budibase/docker-compose.yml
index 3f82de3e9..d1d6744af 100644
--- a/apps/dokploy/templates/budibase/docker-compose.yml
+++ b/apps/dokploy/templates/budibase/docker-compose.yml
@@ -2,8 +2,7 @@ services:
apps:
image: budibase.docker.scarf.sh/budibase/apps:3.2.25
restart: unless-stopped
- networks:
- - dokploy-network
+
environment:
SELF_HOSTED: 1
LOG_LEVEL: info
@@ -43,8 +42,7 @@ services:
worker:
image: budibase.docker.scarf.sh/budibase/worker:3.2.25
restart: unless-stopped
- networks:
- - dokploy-network
+
environment:
SELF_HOSTED: 1
LOG_LEVEL: info
@@ -83,8 +81,7 @@ services:
minio:
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
restart: unless-stopped
- networks:
- - dokploy-network
+
volumes:
- 'minio_data:/data'
environment:
@@ -104,8 +101,7 @@ services:
proxy:
image: budibase/proxy:3.2.25
restart: unless-stopped
- networks:
- - dokploy-network
+
environment:
PROXY_RATE_LIMIT_WEBHOOKS_PER_SECOND: 10
PROXY_RATE_LIMIT_API_PER_SECOND: 20
@@ -137,8 +133,7 @@ services:
couchdb:
image: budibase/couchdb:v3.3.3
restart: unless-stopped
- networks:
- - dokploy-network
+
environment:
COUCHDB_USER: budibase
COUCHDB_PASSWORD: ${BB_COUCHDB_PASSWORD}
@@ -157,8 +152,7 @@ services:
- 'couchdb3_data:/opt/couchdb/data'
redis:
image: redis:7.2-alpine
- networks:
- - dokploy-network
+
restart: unless-stopped
command: 'redis-server --requirepass "${BB_REDIS_PASSWORD}"'
volumes:
@@ -176,8 +170,7 @@ services:
start_period: 10s
watchtower:
restart: unless-stopped
- networks:
- - dokploy-network
+
image: containrrr/watchtower:1.7.1
volumes:
- '/var/run/docker.sock:/var/run/docker.sock'
diff --git a/apps/dokploy/templates/calcom/docker-compose.yml b/apps/dokploy/templates/calcom/docker-compose.yml
index 7a1d8c92a..a309a1da4 100644
--- a/apps/dokploy/templates/calcom/docker-compose.yml
+++ b/apps/dokploy/templates/calcom/docker-compose.yml
@@ -1,8 +1,7 @@
services:
postgres:
image: postgres:16-alpine
- networks:
- - dokploy-network
+
volumes:
- calcom-data:/var/lib/postgresql/data
environment:
diff --git a/apps/dokploy/templates/chatwoot/docker-compose.yml b/apps/dokploy/templates/chatwoot/docker-compose.yml
index 8b762e36e..b24ca0b56 100644
--- a/apps/dokploy/templates/chatwoot/docker-compose.yml
+++ b/apps/dokploy/templates/chatwoot/docker-compose.yml
@@ -51,8 +51,7 @@ services:
restart: always
volumes:
- chatwoot-postgres-data:/var/lib/postgresql/data
- networks:
- - dokploy-network
+
environment:
- POSTGRES_DB=${POSTGRES_DATABASE}
- POSTGRES_USER=${POSTGRES_USERNAME}
@@ -63,8 +62,7 @@ services:
restart: always
volumes:
- chatwoot-redis-data:/data
- networks:
- - dokploy-network
+
networks:
dokploy-network:
diff --git a/apps/dokploy/templates/checkmate/docker-compose.yml b/apps/dokploy/templates/checkmate/docker-compose.yml
index dc83a28f6..7a5fc8984 100644
--- a/apps/dokploy/templates/checkmate/docker-compose.yml
+++ b/apps/dokploy/templates/checkmate/docker-compose.yml
@@ -9,8 +9,7 @@ services:
- 443
depends_on:
- server
- networks:
- - dokploy-network
+
server:
image: bluewaveuptime/uptime_server:latest
restart: always
@@ -22,8 +21,7 @@ services:
environment:
- DB_CONNECTION_STRING=mongodb://mongodb:27017/uptime_db
- REDIS_HOST=redis
- networks:
- - dokploy-network
+
# volumes:
# - /var/run/docker.sock:/var/run/docker.sock:ro
redis:
@@ -33,8 +31,7 @@ services:
- 6379
volumes:
- ../files/redis/data:/data
- networks:
- - dokploy-network
+
mongodb:
image: bluewaveuptime/uptime_database_mongo:latest
restart: always
@@ -43,5 +40,3 @@ services:
command: ["mongod", "--quiet"]
ports:
- 27017
- networks:
- - dokploy-network
\ No newline at end of file
diff --git a/apps/dokploy/templates/cloudflared/index.ts b/apps/dokploy/templates/cloudflared/index.ts
index 661fa31d0..93ea091c6 100644
--- a/apps/dokploy/templates/cloudflared/index.ts
+++ b/apps/dokploy/templates/cloudflared/index.ts
@@ -1,6 +1,6 @@
import type { Schema, Template } from "../utils";
-export function generate(schema: Schema): Template {
+export function generate(_schema: Schema): Template {
const envs = [`CLOUDFLARE_TUNNEL_TOKEN=""`];
return {
diff --git a/apps/dokploy/templates/coder/docker-compose.yml b/apps/dokploy/templates/coder/docker-compose.yml
index 27bb14bd2..875c7ae81 100644
--- a/apps/dokploy/templates/coder/docker-compose.yml
+++ b/apps/dokploy/templates/coder/docker-compose.yml
@@ -1,8 +1,7 @@
services:
coder:
image: ghcr.io/coder/coder:v2.15.3
- networks:
- - dokploy-network
+
volumes:
- /var/run/docker.sock:/var/run/docker.sock
group_add:
@@ -17,8 +16,7 @@ services:
db:
image: postgres:17
- networks:
- - dokploy-network
+
environment:
- POSTGRES_PASSWORD
- POSTGRES_USER
diff --git a/apps/dokploy/templates/convex/docker-compose.yml b/apps/dokploy/templates/convex/docker-compose.yml
new file mode 100644
index 000000000..12e2b5ada
--- /dev/null
+++ b/apps/dokploy/templates/convex/docker-compose.yml
@@ -0,0 +1,37 @@
+services:
+ backend:
+ image: ghcr.io/get-convex/convex-backend:6c974d219776b753cd23d26f4a296629ff7c2cad
+ ports:
+ - "${PORT:-3210}:3210"
+ - "${SITE_PROXY_PORT:-3211}:3211"
+ volumes:
+ - data:/convex/data
+ environment:
+ - INSTANCE_NAME=${INSTANCE_NAME:-}
+ - INSTANCE_SECRET=${INSTANCE_SECRET:-}
+ - CONVEX_RELEASE_VERSION_DEV=${CONVEX_RELEASE_VERSION_DEV:-}
+ - ACTIONS_USER_TIMEOUT_SECS=${ACTIONS_USER_TIMEOUT_SECS:-}
+ - CONVEX_CLOUD_ORIGIN=${CONVEX_CLOUD_ORIGIN:-http://127.0.0.1:3210}
+ - CONVEX_SITE_ORIGIN=${CONVEX_SITE_ORIGIN:-http://127.0.0.1:3211}
+ - DATABASE_URL=${DATABASE_URL:-}
+ - DISABLE_BEACON=${DISABLE_BEACON:-}
+ - REDACT_LOGS_TO_CLIENT=${REDACT_LOGS_TO_CLIENT:-}
+ - RUST_LOG=${RUST_LOG:-info}
+ - RUST_BACKTRACE=${RUST_BACKTRACE:-}
+ healthcheck:
+ test: curl -f http://localhost:3210/version
+ interval: 5s
+ start_period: 5s
+
+ dashboard:
+ image: ghcr.io/get-convex/convex-dashboard:4499dd4fd7f2148687a7774599c613d052950f46
+ ports:
+ - "${DASHBOARD_PORT:-6791}:6791"
+ environment:
+ - NEXT_PUBLIC_DEPLOYMENT_URL=${NEXT_PUBLIC_DEPLOYMENT_URL:-http://127.0.0.1:3210}
+ depends_on:
+ backend:
+ condition: service_healthy
+
+volumes:
+ data:
diff --git a/apps/dokploy/templates/convex/index.ts b/apps/dokploy/templates/convex/index.ts
new file mode 100644
index 000000000..badfe7320
--- /dev/null
+++ b/apps/dokploy/templates/convex/index.ts
@@ -0,0 +1,38 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const dashboardDomain = generateRandomDomain(schema);
+ const backendDomain = generateRandomDomain(schema);
+ const actionsDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: dashboardDomain,
+ port: 6791,
+ serviceName: "dashboard",
+ },
+ {
+ host: backendDomain,
+ port: 3210,
+ serviceName: "backend",
+ },
+ {
+ host: actionsDomain,
+ port: 3211,
+ serviceName: "backend",
+ },
+ ];
+
+ const envs = [
+ `NEXT_PUBLIC_DEPLOYMENT_URL=http://${backendDomain}`,
+ `CONVEX_CLOUD_ORIGIN=http://${backendDomain}`,
+ `CONVEX_SITE_ORIGIN=http://${actionsDomain}`,
+ ];
+
+ return { envs, domains };
+}
diff --git a/apps/dokploy/templates/directus/docker-compose.yml b/apps/dokploy/templates/directus/docker-compose.yml
index 20f1b45d0..52e64baf6 100644
--- a/apps/dokploy/templates/directus/docker-compose.yml
+++ b/apps/dokploy/templates/directus/docker-compose.yml
@@ -3,8 +3,7 @@ services:
image: postgis/postgis:13-master
volumes:
- directus_database:/var/lib/postgresql/data
- networks:
- - dokploy-network
+
environment:
POSTGRES_USER: "directus"
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
@@ -26,8 +25,7 @@ services:
retries: 5
start_interval: 5s
start_period: 30s
- networks:
- - dokploy-network
+
directus:
image: directus/directus:11.0.2
diff --git a/apps/dokploy/templates/discord-tickets/docker-compose.yml b/apps/dokploy/templates/discord-tickets/docker-compose.yml
index e6e41288d..f797a77b0 100644
--- a/apps/dokploy/templates/discord-tickets/docker-compose.yml
+++ b/apps/dokploy/templates/discord-tickets/docker-compose.yml
@@ -4,8 +4,7 @@ services:
tickets-postgres:
image: mysql:8
restart: unless-stopped
- networks:
- - dokploy-network
+
volumes:
- tickets-mysql-data:/var/lib/mysql
environment:
@@ -25,8 +24,7 @@ services:
tickets-postgres:
condition: service_healthy
restart: unless-stopped
- networks:
- - dokploy-network
+
volumes:
- tickets-app-data:/home/container/user
- /etc/timezone:/etc/timezone:ro
diff --git a/apps/dokploy/templates/discourse/docker-compose.yml b/apps/dokploy/templates/discourse/docker-compose.yml
index ce6106be5..2b938b855 100644
--- a/apps/dokploy/templates/discourse/docker-compose.yml
+++ b/apps/dokploy/templates/discourse/docker-compose.yml
@@ -3,8 +3,7 @@ version: '3.7'
services:
discourse-db:
image: docker.io/bitnami/postgresql:17
- networks:
- - dokploy-network
+
volumes:
- discourse-postgresql-data:/bitnami/postgresql
environment:
@@ -20,8 +19,7 @@ services:
discourse-redis:
image: docker.io/bitnami/redis:7.4
- networks:
- - dokploy-network
+
volumes:
- discourse-redis-data:/bitnami/redis
environment:
@@ -35,8 +33,7 @@ services:
discourse-app:
image: docker.io/bitnami/discourse:3.3.2
- networks:
- - dokploy-network
+
volumes:
- discourse-data:/bitnami/discourse
depends_on:
@@ -63,8 +60,7 @@ services:
discourse-sidekiq:
image: docker.io/bitnami/discourse:3.3.2
- networks:
- - dokploy-network
+
volumes:
- discourse-sidekiq-data:/bitnami/discourse
depends_on:
diff --git a/apps/dokploy/templates/docmost/docker-compose.yml b/apps/dokploy/templates/docmost/docker-compose.yml
index a6ebbd4f4..b5995594b 100644
--- a/apps/dokploy/templates/docmost/docker-compose.yml
+++ b/apps/dokploy/templates/docmost/docker-compose.yml
@@ -12,8 +12,7 @@ services:
- DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@db:5432/${POSTGRES_DB}?schema=public
- REDIS_URL=redis://redis:6379
restart: unless-stopped
- networks:
- - dokploy-network
+
volumes:
- docmost:/app/data/storage
@@ -24,16 +23,14 @@ services:
- POSTGRES_USER
- POSTGRES_PASSWORD
restart: unless-stopped
- networks:
- - dokploy-network
+
volumes:
- db_docmost_data:/var/lib/postgresql/data
redis:
image: redis:7.2-alpine
restart: unless-stopped
- networks:
- - dokploy-network
+
volumes:
- redis_docmost_data:/data
diff --git a/apps/dokploy/templates/documenso/docker-compose.yml b/apps/dokploy/templates/documenso/docker-compose.yml
index 562fe4987..9b8e8ed87 100644
--- a/apps/dokploy/templates/documenso/docker-compose.yml
+++ b/apps/dokploy/templates/documenso/docker-compose.yml
@@ -2,8 +2,7 @@ version: "3.8"
services:
postgres:
image: postgres:16
- networks:
- - dokploy-network
+
volumes:
- documenso-data:/var/lib/postgresql/data
environment:
diff --git a/apps/dokploy/templates/drawio/docker-compose.yml b/apps/dokploy/templates/drawio/docker-compose.yml
index 1712363f3..a7d7b578d 100644
--- a/apps/dokploy/templates/drawio/docker-compose.yml
+++ b/apps/dokploy/templates/drawio/docker-compose.yml
@@ -4,16 +4,14 @@ services:
image: plantuml/plantuml-server
ports:
- "8080"
- networks:
- - dokploy-network
+
volumes:
- fonts_volume:/usr/share/fonts/drawio
image-export:
image: jgraph/export-server
ports:
- "8000"
- networks:
- - dokploy-network
+
volumes:
- fonts_volume:/usr/share/fonts/drawio
environment:
@@ -28,8 +26,7 @@ services:
depends_on:
- plantuml-server
- image-export
- networks:
- - dokploy-network
+
environment:
RAWIO_SELF_CONTAINED: 1
DRAWIO_USE_HTTP: 1
diff --git a/apps/dokploy/templates/drawio/index.ts b/apps/dokploy/templates/drawio/index.ts
index e3c57c5a5..701283c8d 100644
--- a/apps/dokploy/templates/drawio/index.ts
+++ b/apps/dokploy/templates/drawio/index.ts
@@ -8,7 +8,7 @@ import {
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
- const secretKeyBase = generateBase64(64);
+ const _secretKeyBase = generateBase64(64);
const domains: DomainSchema[] = [
{
diff --git a/apps/dokploy/templates/erpnext/docker-compose.yml b/apps/dokploy/templates/erpnext/docker-compose.yml
new file mode 100644
index 000000000..28cd8f6ab
--- /dev/null
+++ b/apps/dokploy/templates/erpnext/docker-compose.yml
@@ -0,0 +1,354 @@
+x-custom-image: &custom_image
+ image: ${IMAGE_NAME:-docker.io/frappe/erpnext}:${VERSION:-version-15}
+ pull_policy: ${PULL_POLICY:-always}
+ deploy:
+ restart_policy:
+ condition: always
+
+services:
+ backend:
+ <<: *custom_image
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - '0.0.0.0:8000'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+
+ frontend:
+ <<: *custom_image
+ command:
+ - nginx-entrypoint.sh
+ depends_on:
+ backend:
+ condition: service_started
+ required: true
+ websocket:
+ condition: service_started
+ required: true
+ environment:
+ BACKEND: backend:8000
+ FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host}
+ SOCKETIO: websocket:9000
+ UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
+ UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
+ UPSTREAM_REAL_IP_RECURSIVE: "off"
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+
+ networks:
+ - bench-network
+
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - '0.0.0.0:8080'
+ interval: 2s
+ timeout: 30s
+ retries: 30
+
+ queue-default:
+ <<: *custom_image
+ command:
+ - bench
+ - worker
+ - --queue
+ - default
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - 'redis-queue:6379'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+ depends_on:
+ configurator:
+ condition: service_completed_successfully
+ required: true
+
+ queue-long:
+ <<: *custom_image
+ command:
+ - bench
+ - worker
+ - --queue
+ - long
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - 'redis-queue:6379'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+ depends_on:
+ configurator:
+ condition: service_completed_successfully
+ required: true
+
+ queue-short:
+ <<: *custom_image
+ command:
+ - bench
+ - worker
+ - --queue
+ - short
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - 'redis-queue:6379'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+ depends_on:
+ configurator:
+ condition: service_completed_successfully
+ required: true
+
+ scheduler:
+ <<: *custom_image
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - 'redis-queue:6379'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+ command:
+ - bench
+ - schedule
+ depends_on:
+ configurator:
+ condition: service_completed_successfully
+ required: true
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+
+ websocket:
+ <<: *custom_image
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - '0.0.0.0:9000'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+ command:
+ - node
+ - /home/frappe/frappe-bench/apps/frappe/socketio.js
+ depends_on:
+ configurator:
+ condition: service_completed_successfully
+ required: true
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+
+ configurator:
+ <<: *custom_image
+ deploy:
+ mode: replicated
+ replicas: ${CONFIGURE:-0}
+ restart_policy:
+ condition: none
+ entrypoint: ["bash", "-c"]
+ command:
+ - >
+ [[ $${REGENERATE_APPS_TXT} == "1" ]] && ls -1 apps > sites/apps.txt;
+ [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && exit 0;
+ bench set-config -g db_host $$DB_HOST;
+ bench set-config -gp db_port $$DB_PORT;
+ bench set-config -g redis_cache "redis://$$REDIS_CACHE";
+ bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
+ bench set-config -g redis_socketio "redis://$$REDIS_QUEUE";
+ bench set-config -gp socketio_port $$SOCKETIO_PORT;
+ environment:
+ DB_HOST: "${DB_HOST:-db}"
+ DB_PORT: "3306"
+ REDIS_CACHE: redis-cache:6379
+ REDIS_QUEUE: redis-queue:6379
+ SOCKETIO_PORT: "9000"
+ REGENERATE_APPS_TXT: "${REGENERATE_APPS_TXT:-0}"
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+
+ create-site:
+ <<: *custom_image
+ deploy:
+ mode: replicated
+ replicas: ${CREATE_SITE:-0}
+ restart_policy:
+ condition: none
+ entrypoint: ["bash", "-c"]
+ command:
+ - >
+ wait-for-it -t 120 $$DB_HOST:$$DB_PORT;
+ wait-for-it -t 120 redis-cache:6379;
+ wait-for-it -t 120 redis-queue:6379;
+ export start=`date +%s`;
+ until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
+ [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
+ [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
+ do
+ echo "Waiting for sites/common_site_config.json to be created";
+ sleep 5;
+ if (( `date +%s`-start > 120 )); then
+ echo "could not find sites/common_site_config.json with required keys";
+ exit 1
+ fi
+ done;
+ echo "sites/common_site_config.json found";
+ [[ -d "sites/${SITE_NAME}" ]] && echo "${SITE_NAME} already exists" && exit 0;
+ bench new-site --mariadb-user-host-login-scope='%' --admin-password=$${ADMIN_PASSWORD} --db-root-username=root --db-root-password=$${DB_ROOT_PASSWORD} $${INSTALL_APP_ARGS} $${SITE_NAME};
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ environment:
+ SITE_NAME: ${SITE_NAME}
+ ADMIN_PASSWORD: ${ADMIN_PASSWORD}
+ DB_HOST: ${DB_HOST:-db}
+ DB_PORT: "${DB_PORT:-3306}"
+ DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
+ INSTALL_APP_ARGS: ${INSTALL_APP_ARGS}
+ networks:
+ - bench-network
+
+ migration:
+ <<: *custom_image
+ deploy:
+ mode: replicated
+ replicas: ${MIGRATE:-0}
+ restart_policy:
+ condition: none
+ entrypoint: ["bash", "-c"]
+ command:
+ - >
+ curl -f http://${SITE_NAME}:8080/api/method/ping || echo "Site busy" && exit 0;
+ bench --site all set-config -p maintenance_mode 1;
+ bench --site all set-config -p pause_scheduler 1;
+ bench --site all migrate;
+ bench --site all set-config -p maintenance_mode 0;
+ bench --site all set-config -p pause_scheduler 0;
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+
+ db:
+ image: mariadb:10.6
+ deploy:
+ mode: replicated
+ replicas: ${ENABLE_DB:-0}
+ restart_policy:
+ condition: always
+ healthcheck:
+ test: mysqladmin ping -h localhost --password=${DB_ROOT_PASSWORD}
+ interval: 1s
+ retries: 20
+ command:
+ - --character-set-server=utf8mb4
+ - --collation-server=utf8mb4_unicode_ci
+ - --skip-character-set-client-handshake
+ - --skip-innodb-read-only-compressed
+ environment:
+ - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
+ - MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
+ volumes:
+ - db-data:/var/lib/mysql
+ networks:
+ - bench-network
+
+ redis-cache:
+ deploy:
+ restart_policy:
+ condition: always
+ image: redis:6.2-alpine
+ volumes:
+ - redis-cache-data:/data
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - redis-cli
+ - ping
+ interval: 5s
+ timeout: 5s
+ retries: 3
+
+ redis-queue:
+ deploy:
+ restart_policy:
+ condition: always
+ image: redis:6.2-alpine
+ volumes:
+ - redis-queue-data:/data
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - redis-cli
+ - ping
+ interval: 5s
+ timeout: 5s
+ retries: 3
+
+ redis-socketio:
+ deploy:
+ restart_policy:
+ condition: always
+ image: redis:6.2-alpine
+ volumes:
+ - redis-socketio-data:/data
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - redis-cli
+ - ping
+ interval: 5s
+ timeout: 5s
+ retries: 3
+
+volumes:
+ db-data:
+ redis-cache-data:
+ redis-queue-data:
+ redis-socketio-data:
+ sites:
+ driver_opts:
+ type: "${SITE_VOLUME_TYPE}"
+ o: "${SITE_VOLUME_OPTS}"
+ device: "${SITE_VOLUME_DEV}"
+
+networks:
+ bench-network:
\ No newline at end of file
diff --git a/apps/dokploy/templates/erpnext/index.ts b/apps/dokploy/templates/erpnext/index.ts
new file mode 100644
index 000000000..5b7543b91
--- /dev/null
+++ b/apps/dokploy/templates/erpnext/index.ts
@@ -0,0 +1,39 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const dbRootPassword = generatePassword(32);
+ const adminPassword = generatePassword(32);
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 8080,
+ serviceName: "frontend",
+ },
+ ];
+
+ const envs = [
+ `SITE_NAME=${mainDomain}`,
+ `ADMIN_PASSWORD=${adminPassword}`,
+ `DB_ROOT_PASSWORD=${dbRootPassword}`,
+ "MIGRATE=1",
+ "ENABLE_DB=1",
+ "DB_HOST=db",
+ "CREATE_SITE=1",
+ "CONFIGURE=1",
+ "REGENERATE_APPS_TXT=1",
+ "INSTALL_APP_ARGS=--install-app erpnext",
+ "IMAGE_NAME=docker.io/frappe/erpnext",
+ "VERSION=version-15",
+ "FRAPPE_SITE_NAME_HEADER=",
+ ];
+
+ return { envs, domains };
+}
diff --git a/apps/dokploy/templates/evolutionapi/docker-compose.yml b/apps/dokploy/templates/evolutionapi/docker-compose.yml
new file mode 100644
index 000000000..d4803de1c
--- /dev/null
+++ b/apps/dokploy/templates/evolutionapi/docker-compose.yml
@@ -0,0 +1,58 @@
+services:
+ evolution-api:
+ image: atendai/evolution-api:v2.1.2
+ restart: always
+ volumes:
+ - evolution-instances:/evolution/instances
+
+ environment:
+ - SERVER_URL=${SERVER_URL}
+ - AUTHENTICATION_TYPE=${AUTHENTICATION_TYPE}
+ - AUTHENTICATION_API_KEY=${AUTHENTICATION_API_KEY}
+ - AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=${AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES}
+ - LANGUAGE=${LANGUAGE}
+ - CONFIG_SESSION_PHONE_CLIENT=${CONFIG_SESSION_PHONE_CLIENT}
+ - CONFIG_SESSION_PHONE_NAME=${CONFIG_SESSION_PHONE_NAME}
+ - TELEMETRY=${TELEMETRY}
+ - TELEMETRY_URL=${TELEMETRY_URL}
+ - DATABASE_ENABLED=${DATABASE_ENABLED}
+ - DATABASE_PROVIDER=${DATABASE_PROVIDER}
+ - DATABASE_CONNECTION_URI=${DATABASE_CONNECTION_URI}
+ - DATABASE_SAVE_DATA_INSTANCE=${DATABASE_SAVE_DATA_INSTANCE}
+ - DATABASE_SAVE_DATA_NEW_MESSAGE=${DATABASE_SAVE_DATA_NEW_MESSAGE}
+ - DATABASE_SAVE_MESSAGE_UPDATE=${DATABASE_SAVE_MESSAGE_UPDATE}
+ - DATABASE_SAVE_DATA_CONTACTS=${DATABASE_SAVE_DATA_CONTACTS}
+ - DATABASE_SAVE_DATA_CHATS=${DATABASE_SAVE_DATA_CHATS}
+ - DATABASE_SAVE_DATA_LABELS=${DATABASE_SAVE_DATA_LABELS}
+ - DATABASE_SAVE_DATA_HISTORIC=${DATABASE_SAVE_DATA_HISTORIC}
+ - CACHE_REDIS_ENABLED=${CACHE_REDIS_ENABLED}
+ - CACHE_REDIS_URI=${CACHE_REDIS_URI}
+ - CACHE_REDIS_PREFIX_KEY=${CACHE_REDIS_PREFIX_KEY}
+ - CACHE_REDIS_SAVE_INSTANCES=${CACHE_REDIS_SAVE_INSTANCES}
+
+ evolution-postgres:
+ image: postgres:16-alpine
+ restart: always
+ volumes:
+ - evolution-postgres-data:/var/lib/postgresql/data
+
+ environment:
+ - POSTGRES_DB=${POSTGRES_DATABASE}
+ - POSTGRES_USER=${POSTGRES_USERNAME}
+ - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
+
+ evolution-redis:
+ image: redis:alpine
+ restart: always
+ volumes:
+ - evolution-redis-data:/data
+
+
+networks:
+ dokploy-network:
+ external: true
+
+volumes:
+ evolution-instances:
+ evolution-postgres-data:
+ evolution-redis-data:
\ No newline at end of file
diff --git a/apps/dokploy/templates/evolutionapi/index.ts b/apps/dokploy/templates/evolutionapi/index.ts
new file mode 100644
index 000000000..6ca7a3b60
--- /dev/null
+++ b/apps/dokploy/templates/evolutionapi/index.ts
@@ -0,0 +1,59 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const apiKey = generateBase64(64);
+ const postgresPassword = generatePassword();
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 8080,
+ serviceName: "evolution-api",
+ },
+ ];
+
+ const envs = [
+ `SERVER_URL=https://${mainDomain}`,
+ "AUTHENTICATION_TYPE=apikey",
+ `AUTHENTICATION_API_KEY=${apiKey}`,
+ "AUTHENTICATION_EXPOSE_IN_FETCH_INSTANCES=true",
+
+ "LANGUAGE=en",
+ "CONFIG_SESSION_PHONE_CLIENT=Evolution API",
+ "CONFIG_SESSION_PHONE_NAME=Chrome",
+ "TELEMETRY=false",
+ "TELEMETRY_URL=",
+
+ "POSTGRES_DATABASE=evolution",
+ "POSTGRES_USERNAME=postgresql",
+ `POSTGRES_PASSWORD=${postgresPassword}`,
+ "DATABASE_ENABLED=true",
+ "DATABASE_PROVIDER=postgresql",
+ `DATABASE_CONNECTION_URI=postgres://postgresql:${postgresPassword}@evolution-postgres:5432/evolution`,
+ "DATABASE_SAVE_DATA_INSTANCE=true",
+ "DATABASE_SAVE_DATA_NEW_MESSAGE=true",
+ "DATABASE_SAVE_MESSAGE_UPDATE=true",
+ "DATABASE_SAVE_DATA_CONTACTS=true",
+ "DATABASE_SAVE_DATA_CHATS=true",
+ "DATABASE_SAVE_DATA_LABELS=true",
+ "DATABASE_SAVE_DATA_HISTORIC=true",
+
+ "CACHE_REDIS_ENABLED=true",
+ "CACHE_REDIS_URI=redis://evolution-redis:6379",
+ "CACHE_REDIS_PREFIX_KEY=evolution",
+ "CACHE_REDIS_SAVE_INSTANCES=true",
+ ];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/excalidraw/docker-compose.yml b/apps/dokploy/templates/excalidraw/docker-compose.yml
index 8743434b6..3cf2fb1d1 100644
--- a/apps/dokploy/templates/excalidraw/docker-compose.yml
+++ b/apps/dokploy/templates/excalidraw/docker-compose.yml
@@ -2,6 +2,5 @@ version: "3.8"
services:
excalidraw:
- networks:
- - dokploy-network
+
image: excalidraw/excalidraw:latest
diff --git a/apps/dokploy/templates/excalidraw/index.ts b/apps/dokploy/templates/excalidraw/index.ts
index 13a43c440..7f73f395f 100644
--- a/apps/dokploy/templates/excalidraw/index.ts
+++ b/apps/dokploy/templates/excalidraw/index.ts
@@ -2,7 +2,6 @@ import {
type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
diff --git a/apps/dokploy/templates/formbricks/docker-compose.yml b/apps/dokploy/templates/formbricks/docker-compose.yml
new file mode 100644
index 000000000..ad1dcbcff
--- /dev/null
+++ b/apps/dokploy/templates/formbricks/docker-compose.yml
@@ -0,0 +1,37 @@
+x-environment: &environment
+ environment:
+ WEBAPP_URL: ${WEBAPP_URL}
+ NEXTAUTH_URL: ${NEXTAUTH_URL}
+ DATABASE_URL: "postgresql://postgres:postgres@postgres:5432/formbricks?schema=public"
+ NEXTAUTH_SECRET: ${NEXTAUTH_SECRET}
+ ENCRYPTION_KEY: ${ENCRYPTION_KEY}
+ CRON_SECRET: ${CRON_SECRET}
+ EMAIL_VERIFICATION_DISABLED: 1
+ PASSWORD_RESET_DISABLED: 1
+ S3_FORCE_PATH_STYLE: 0
+
+services:
+ postgres:
+ restart: always
+ image: pgvector/pgvector:pg17
+ volumes:
+ - postgres:/var/lib/postgresql/data
+ environment:
+ - POSTGRES_PASSWORD=postgres
+
+
+ formbricks:
+ restart: always
+ image: ghcr.io/formbricks/formbricks:v3.1.3
+ depends_on:
+ - postgres
+ ports:
+ - 3000
+ volumes:
+ - ../files/uploads:/home/nextjs/apps/web/uploads/
+ <<: *environment
+
+volumes:
+ postgres:
+ driver: local
+ uploads:
diff --git a/apps/dokploy/templates/formbricks/index.ts b/apps/dokploy/templates/formbricks/index.ts
new file mode 100644
index 000000000..fc179f497
--- /dev/null
+++ b/apps/dokploy/templates/formbricks/index.ts
@@ -0,0 +1,38 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const secretBase = generateBase64(64);
+ const encryptionKey = generateBase64(48);
+ const cronSecret = generateBase64(32);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "formbricks",
+ },
+ ];
+
+ const envs = [
+ `WEBAPP_URL=http://${mainDomain}`,
+ `NEXTAUTH_URL=http://${mainDomain}`,
+ `NEXTAUTH_SECRET=${secretBase}`,
+ `ENCRYPTION_KEY=${encryptionKey}`,
+ `CRON_SECRET=${cronSecret}`,
+ ];
+
+ const mounts: Template["mounts"] = [];
+
+ return {
+ envs,
+ mounts,
+ domains,
+ };
+}
diff --git a/apps/dokploy/templates/frappe-hr/docker-compose.yml b/apps/dokploy/templates/frappe-hr/docker-compose.yml
new file mode 100644
index 000000000..a7ce9b262
--- /dev/null
+++ b/apps/dokploy/templates/frappe-hr/docker-compose.yml
@@ -0,0 +1,354 @@
+x-custom-image: &custom_image
+ image: ${IMAGE_NAME:-ghcr.io/frappe/hrms}:${VERSION:-version-15}
+ pull_policy: ${PULL_POLICY:-always}
+ deploy:
+ restart_policy:
+ condition: always
+
+services:
+ backend:
+ <<: *custom_image
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - '0.0.0.0:8000'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+
+ frontend:
+ <<: *custom_image
+ command:
+ - nginx-entrypoint.sh
+ depends_on:
+ backend:
+ condition: service_started
+ required: true
+ websocket:
+ condition: service_started
+ required: true
+ environment:
+ BACKEND: backend:8000
+ FRAPPE_SITE_NAME_HEADER: ${FRAPPE_SITE_NAME_HEADER:-$$host}
+ SOCKETIO: websocket:9000
+ UPSTREAM_REAL_IP_ADDRESS: 127.0.0.1
+ UPSTREAM_REAL_IP_HEADER: X-Forwarded-For
+ UPSTREAM_REAL_IP_RECURSIVE: "off"
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+
+ networks:
+ - bench-network
+
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - '0.0.0.0:8080'
+ interval: 2s
+ timeout: 30s
+ retries: 30
+
+ queue-default:
+ <<: *custom_image
+ command:
+ - bench
+ - worker
+ - --queue
+ - default
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - 'redis-queue:6379'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+ depends_on:
+ configurator:
+ condition: service_completed_successfully
+ required: true
+
+ queue-long:
+ <<: *custom_image
+ command:
+ - bench
+ - worker
+ - --queue
+ - long
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - 'redis-queue:6379'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+ depends_on:
+ configurator:
+ condition: service_completed_successfully
+ required: true
+
+ queue-short:
+ <<: *custom_image
+ command:
+ - bench
+ - worker
+ - --queue
+ - short
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - 'redis-queue:6379'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+ depends_on:
+ configurator:
+ condition: service_completed_successfully
+ required: true
+
+ scheduler:
+ <<: *custom_image
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - 'redis-queue:6379'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+ command:
+ - bench
+ - schedule
+ depends_on:
+ configurator:
+ condition: service_completed_successfully
+ required: true
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+
+ websocket:
+ <<: *custom_image
+ healthcheck:
+ test:
+ - CMD
+ - wait-for-it
+ - '0.0.0.0:9000'
+ interval: 2s
+ timeout: 10s
+ retries: 30
+ command:
+ - node
+ - /home/frappe/frappe-bench/apps/frappe/socketio.js
+ depends_on:
+ configurator:
+ condition: service_completed_successfully
+ required: true
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+
+ configurator:
+ <<: *custom_image
+ deploy:
+ mode: replicated
+ replicas: ${CONFIGURE:-0}
+ restart_policy:
+ condition: none
+ entrypoint: ["bash", "-c"]
+ command:
+ - >
+ [[ $${REGENERATE_APPS_TXT} == "1" ]] && ls -1 apps > sites/apps.txt;
+ [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && exit 0;
+ bench set-config -g db_host $$DB_HOST;
+ bench set-config -gp db_port $$DB_PORT;
+ bench set-config -g redis_cache "redis://$$REDIS_CACHE";
+ bench set-config -g redis_queue "redis://$$REDIS_QUEUE";
+ bench set-config -g redis_socketio "redis://$$REDIS_QUEUE";
+ bench set-config -gp socketio_port $$SOCKETIO_PORT;
+ environment:
+ DB_HOST: "${DB_HOST:-db}"
+ DB_PORT: "3306"
+ REDIS_CACHE: redis-cache:6379
+ REDIS_QUEUE: redis-queue:6379
+ SOCKETIO_PORT: "9000"
+ REGENERATE_APPS_TXT: "${REGENERATE_APPS_TXT:-0}"
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+
+ create-site:
+ <<: *custom_image
+ deploy:
+ mode: replicated
+ replicas: ${CREATE_SITE:-0}
+ restart_policy:
+ condition: none
+ entrypoint: ["bash", "-c"]
+ command:
+ - >
+ wait-for-it -t 120 $$DB_HOST:$$DB_PORT;
+ wait-for-it -t 120 redis-cache:6379;
+ wait-for-it -t 120 redis-queue:6379;
+ export start=`date +%s`;
+ until [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".db_host // empty"` ]] && \
+ [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_cache // empty"` ]] && \
+ [[ -n `grep -hs ^ sites/common_site_config.json | jq -r ".redis_queue // empty"` ]];
+ do
+ echo "Waiting for sites/common_site_config.json to be created";
+ sleep 5;
+ if (( `date +%s`-start > 120 )); then
+ echo "could not find sites/common_site_config.json with required keys";
+ exit 1
+ fi
+ done;
+ echo "sites/common_site_config.json found";
+ [[ -d "sites/${SITE_NAME}" ]] && echo "${SITE_NAME} already exists" && exit 0;
+ bench new-site --mariadb-user-host-login-scope='%' --admin-password=$${ADMIN_PASSWORD} --db-root-username=root --db-root-password=$${DB_ROOT_PASSWORD} $${INSTALL_APP_ARGS} $${SITE_NAME};
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ environment:
+ SITE_NAME: ${SITE_NAME}
+ ADMIN_PASSWORD: ${ADMIN_PASSWORD}
+ DB_HOST: ${DB_HOST:-db}
+ DB_PORT: "${DB_PORT:-3306}"
+ DB_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
+ INSTALL_APP_ARGS: ${INSTALL_APP_ARGS}
+ networks:
+ - bench-network
+
+ migration:
+ <<: *custom_image
+ deploy:
+ mode: replicated
+ replicas: ${MIGRATE:-0}
+ restart_policy:
+ condition: none
+ entrypoint: ["bash", "-c"]
+ command:
+ - >
+ curl -f http://${SITE_NAME}:8080/api/method/ping || echo "Site busy" && exit 0;
+ bench --site all set-config -p maintenance_mode 1;
+ bench --site all set-config -p pause_scheduler 1;
+ bench --site all migrate;
+ bench --site all set-config -p maintenance_mode 0;
+ bench --site all set-config -p pause_scheduler 0;
+ volumes:
+ - sites:/home/frappe/frappe-bench/sites
+ networks:
+ - bench-network
+
+ db:
+ image: mariadb:10.6
+ deploy:
+ mode: replicated
+ replicas: ${ENABLE_DB:-0}
+ restart_policy:
+ condition: always
+ healthcheck:
+ test: mysqladmin ping -h localhost --password=${DB_ROOT_PASSWORD}
+ interval: 1s
+ retries: 20
+ command:
+ - --character-set-server=utf8mb4
+ - --collation-server=utf8mb4_unicode_ci
+ - --skip-character-set-client-handshake
+ - --skip-innodb-read-only-compressed
+ environment:
+ - MYSQL_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
+ - MARIADB_ROOT_PASSWORD=${DB_ROOT_PASSWORD}
+ volumes:
+ - db-data:/var/lib/mysql
+ networks:
+ - bench-network
+
+ redis-cache:
+ deploy:
+ restart_policy:
+ condition: always
+ image: redis:6.2-alpine
+ volumes:
+ - redis-cache-data:/data
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - redis-cli
+ - ping
+ interval: 5s
+ timeout: 5s
+ retries: 3
+
+ redis-queue:
+ deploy:
+ restart_policy:
+ condition: always
+ image: redis:6.2-alpine
+ volumes:
+ - redis-queue-data:/data
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - redis-cli
+ - ping
+ interval: 5s
+ timeout: 5s
+ retries: 3
+
+ redis-socketio:
+ deploy:
+ restart_policy:
+ condition: always
+ image: redis:6.2-alpine
+ volumes:
+ - redis-socketio-data:/data
+ networks:
+ - bench-network
+ healthcheck:
+ test:
+ - CMD
+ - redis-cli
+ - ping
+ interval: 5s
+ timeout: 5s
+ retries: 3
+
+volumes:
+ db-data:
+ redis-cache-data:
+ redis-queue-data:
+ redis-socketio-data:
+ sites:
+ driver_opts:
+ type: "${SITE_VOLUME_TYPE}"
+ o: "${SITE_VOLUME_OPTS}"
+ device: "${SITE_VOLUME_DEV}"
+
+networks:
+ bench-network:
\ No newline at end of file
diff --git a/apps/dokploy/templates/frappe-hr/index.ts b/apps/dokploy/templates/frappe-hr/index.ts
new file mode 100644
index 000000000..1e6b94745
--- /dev/null
+++ b/apps/dokploy/templates/frappe-hr/index.ts
@@ -0,0 +1,39 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const dbRootPassword = generatePassword(32);
+ const adminPassword = generatePassword(32);
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 8080,
+ serviceName: "frontend",
+ },
+ ];
+
+ const envs = [
+ `SITE_NAME=${mainDomain}`,
+ `ADMIN_PASSWORD=${adminPassword}`,
+ `DB_ROOT_PASSWORD=${dbRootPassword}`,
+ "MIGRATE=1",
+ "ENABLE_DB=1",
+ "DB_HOST=db",
+ "CREATE_SITE=1",
+ "CONFIGURE=1",
+ "REGENERATE_APPS_TXT=1",
+ "INSTALL_APP_ARGS=--install-app hrms",
+ "IMAGE_NAME=ghcr.io/frappe/hrms",
+ "VERSION=version-15",
+ "FRAPPE_SITE_NAME_HEADER=",
+ ];
+
+ return { envs, domains };
+}
diff --git a/apps/dokploy/templates/ghost/docker-compose.yml b/apps/dokploy/templates/ghost/docker-compose.yml
index 288c59e54..33c47f7f8 100644
--- a/apps/dokploy/templates/ghost/docker-compose.yml
+++ b/apps/dokploy/templates/ghost/docker-compose.yml
@@ -17,8 +17,7 @@ services:
db:
image: mysql:8.0
restart: always
- networks:
- - dokploy-network
+
environment:
MYSQL_ROOT_PASSWORD: example
volumes:
diff --git a/apps/dokploy/templates/ghost/index.ts b/apps/dokploy/templates/ghost/index.ts
index 1a88c3629..052b7c6bb 100644
--- a/apps/dokploy/templates/ghost/index.ts
+++ b/apps/dokploy/templates/ghost/index.ts
@@ -2,7 +2,6 @@ import {
type DomainSchema,
type Schema,
type Template,
- generateHash,
generateRandomDomain,
} from "../utils";
diff --git a/apps/dokploy/templates/gitea/docker-compose.yml b/apps/dokploy/templates/gitea/docker-compose.yml
index 72e0754e2..5127224cd 100644
--- a/apps/dokploy/templates/gitea/docker-compose.yml
+++ b/apps/dokploy/templates/gitea/docker-compose.yml
@@ -11,8 +11,7 @@ services:
- GITEA__database__USER=gitea
- GITEA__database__PASSWD=gitea
restart: always
- networks:
- - dokploy-network
+
volumes:
- gitea_server:/data
- /etc/timezone:/etc/timezone:ro
@@ -27,8 +26,7 @@ services:
- POSTGRES_USER=gitea
- POSTGRES_PASSWORD=gitea
- POSTGRES_DB=gitea
- networks:
- - dokploy-network
+
volumes:
- gitea_db:/var/lib/postgresql/data
diff --git a/apps/dokploy/templates/glance/docker-compose.yml b/apps/dokploy/templates/glance/docker-compose.yml
new file mode 100644
index 000000000..ace8bc940
--- /dev/null
+++ b/apps/dokploy/templates/glance/docker-compose.yml
@@ -0,0 +1,11 @@
+services:
+ glance:
+ image: glanceapp/glance
+ volumes:
+ - ../files/app/config/:/app/config
+ - ../files/app/assets:/app/assets
+ # Optionally, also mount docker socket if you want to use the docker containers widget
+ # - /var/run/docker.sock:/var/run/docker.sock:ro
+ ports:
+ - 8080
+ env_file: .env
\ No newline at end of file
diff --git a/apps/dokploy/templates/glance/index.ts b/apps/dokploy/templates/glance/index.ts
new file mode 100644
index 000000000..a0ab1b676
--- /dev/null
+++ b/apps/dokploy/templates/glance/index.ts
@@ -0,0 +1,108 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 8080,
+ serviceName: "glance",
+ },
+ ];
+
+ const mounts: Template["mounts"] = [
+ {
+ filePath: "/app/config/glance.yml",
+ content: `
+branding:
+ hide-footer: true
+ logo-text: P
+
+pages:
+ - name: Home
+ columns:
+ - size: small
+ widgets:
+ - type: calendar
+
+ - type: releases
+ show-source-icon: true
+ repositories:
+ - Dokploy/dokploy
+ - n8n-io/n8n
+ - Budibase/budibase
+ - home-assistant/core
+ - tidbyt/pixlet
+
+ - type: twitch-channels
+ channels:
+ - nmplol
+ - extraemily
+ - qtcinderella
+ - ludwig
+ - timthetatman
+ - mizkif
+
+ - size: full
+ widgets:
+ - type: hacker-news
+
+ - type: videos
+ style: grid-cards
+ channels:
+ - UC3GzdWYwUYI1ACxuP9Nm-eg
+ - UCGbg3DjQdcqWwqOLHpYHXIg
+ - UC24RSoLcjiNZbQcT54j5l7Q
+ limit: 3
+
+ - type: rss
+ limit: 10
+ collapse-after: 3
+ cache: 3h
+ feeds:
+ - url: https://daringfireball.net/feeds/main
+ title: Daring Fireball
+
+ - size: small
+ widgets:
+ - type: weather
+ location: Gansevoort, New York, United States
+ show-area-name: false
+ units: imperial
+ hour-format: 12h
+
+ - type: markets
+ markets:
+ - symbol: SPY
+ name: S&P 500
+ - symbol: VOO
+ name: Vanguard
+ - symbol: BTC-USD
+ name: Bitcoin
+ - symbol: ETH-USD
+ name: Etherium
+ - symbol: NVDA
+ name: NVIDIA
+ - symbol: AAPL
+ name: Apple
+ - symbol: MSFT
+ name: Microsoft
+ - symbol: GOOGL
+ name: Google
+ - symbol: AMD
+ name: AMD
+ - symbol: TSLA
+ name: Tesla`,
+ },
+ ];
+
+ return {
+ domains,
+ mounts,
+ };
+}
diff --git a/apps/dokploy/templates/glitchtip/docker-compose.yml b/apps/dokploy/templates/glitchtip/docker-compose.yml
index e45c76627..f47742f01 100644
--- a/apps/dokploy/templates/glitchtip/docker-compose.yml
+++ b/apps/dokploy/templates/glitchtip/docker-compose.yml
@@ -20,13 +20,11 @@ services:
restart: unless-stopped
volumes:
- pg-data:/var/lib/postgresql/data
- networks:
- - dokploy-network
+
redis:
image: redis
restart: unless-stopped
- networks:
- - dokploy-network
+
web:
image: glitchtip/glitchtip:v4.0
depends_on: *default-depends_on
@@ -44,15 +42,13 @@ services:
restart: unless-stopped
volumes:
- uploads:/code/uploads
- networks:
- - dokploy-network
+
migrate:
image: glitchtip/glitchtip:v4.0
depends_on: *default-depends_on
command: "./manage.py migrate"
environment: *default-environment
- networks:
- - dokploy-network
+
volumes:
pg-data:
diff --git a/apps/dokploy/templates/glpi/docker-compose.yml b/apps/dokploy/templates/glpi/docker-compose.yml
index f15cdafef..fa732fa36 100644
--- a/apps/dokploy/templates/glpi/docker-compose.yml
+++ b/apps/dokploy/templates/glpi/docker-compose.yml
@@ -4,8 +4,7 @@ services:
restart: always
volumes:
- glpi-mysql-data:/var/lib/mysql
- networks:
- - dokploy-network
+
glpi-web:
image: elestio/glpi:10.0.16
@@ -16,8 +15,7 @@ services:
- glpi-www-data:/var/www/html/glpi
environment:
- TIMEZONE=Europe/Brussels
- networks:
- - dokploy-network
+
volumes:
glpi-mysql-data:
diff --git a/apps/dokploy/templates/hi-events/docker-compose.yml b/apps/dokploy/templates/hi-events/docker-compose.yml
index 0ce5b7e75..cce45fecf 100644
--- a/apps/dokploy/templates/hi-events/docker-compose.yml
+++ b/apps/dokploy/templates/hi-events/docker-compose.yml
@@ -28,8 +28,7 @@ services:
postgres:
image: elestio/postgres:16
restart: always
- networks:
- - dokploy-network
+
environment:
- POSTGRES_DB
- POSTGRES_USER
diff --git a/apps/dokploy/templates/homarr/docker-compose.yml b/apps/dokploy/templates/homarr/docker-compose.yml
new file mode 100644
index 000000000..876ea3f6f
--- /dev/null
+++ b/apps/dokploy/templates/homarr/docker-compose.yml
@@ -0,0 +1,11 @@
+services:
+ homarr:
+ image: ghcr.io/homarr-labs/homarr:latest
+ restart: unless-stopped
+ volumes:
+ # - /var/run/docker.sock:/var/run/docker.sock # Optional, only if you want docker integration
+ - ../homarr/appdata:/appdata
+ environment:
+ - SECRET_ENCRYPTION_KEY=${SECRET_ENCRYPTION_KEY}
+ ports:
+ - 7575
diff --git a/apps/dokploy/templates/homarr/index.ts b/apps/dokploy/templates/homarr/index.ts
new file mode 100644
index 000000000..eb5a9f823
--- /dev/null
+++ b/apps/dokploy/templates/homarr/index.ts
@@ -0,0 +1,27 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const secretKey = generatePassword(64);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 7575,
+ serviceName: "homarr",
+ },
+ ];
+
+ const envs = [`SECRET_ENCRYPTION_KEY=${secretKey}`];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/huly/docker-compose.yml b/apps/dokploy/templates/huly/docker-compose.yml
index 471ee7e9f..639b10d2f 100644
--- a/apps/dokploy/templates/huly/docker-compose.yml
+++ b/apps/dokploy/templates/huly/docker-compose.yml
@@ -2,8 +2,7 @@ name: ${DOCKER_NAME}
version: "3"
services:
nginx:
- networks:
- - dokploy-network
+
image: "nginx:1.21.3"
ports:
- 80
@@ -12,8 +11,7 @@ services:
restart: unless-stopped
mongodb:
- networks:
- - dokploy-network
+
image: "mongo:7-jammy"
environment:
- PUID=1000
@@ -23,8 +21,7 @@ services:
restart: unless-stopped
minio:
- networks:
- - dokploy-network
+
image: "minio/minio:RELEASE.2024-11-07T00-52-20Z"
command: server /data --address ":9000" --console-address ":9001"
volumes:
@@ -32,8 +29,7 @@ services:
restart: unless-stopped
elastic:
- networks:
- - dokploy-network
+
image: "elasticsearch:7.14.2"
command: |
/bin/sh -c "./bin/elasticsearch-plugin list | grep -q ingest-attachment || yes | ./bin/elasticsearch-plugin install --silent ingest-attachment;
@@ -54,8 +50,7 @@ services:
restart: unless-stopped
rekoni:
- networks:
- - dokploy-network
+
image: hardcoreeng/rekoni-service:${HULY_VERSION}
environment:
- SECRET=${SECRET}
@@ -66,8 +61,7 @@ services:
restart: unless-stopped
transactor:
- networks:
- - dokploy-network
+
image: hardcoreeng/transactor:${HULY_VERSION}
environment:
- SERVER_PORT=3333
@@ -84,8 +78,7 @@ services:
restart: unless-stopped
collaborator:
- networks:
- - dokploy-network
+
image: hardcoreeng/collaborator:${HULY_VERSION}
environment:
- COLLABORATOR_PORT=3078
@@ -97,8 +90,7 @@ services:
restart: unless-stopped
account:
- networks:
- - dokploy-network
+
image: hardcoreeng/account:${HULY_VERSION}
environment:
- SERVER_PORT=3000
@@ -115,8 +107,7 @@ services:
restart: unless-stopped
workspace:
- networks:
- - dokploy-network
+
image: hardcoreeng/workspace:${HULY_VERSION}
environment:
- SERVER_SECRET=${SECRET}
@@ -130,8 +121,7 @@ services:
restart: unless-stopped
front:
- networks:
- - dokploy-network
+
image: hardcoreeng/front:${HULY_VERSION}
environment:
- SERVER_PORT=8080
@@ -156,8 +146,7 @@ services:
restart: unless-stopped
fulltext:
- networks:
- - dokploy-network
+
image: hardcoreeng/fulltext:${HULY_VERSION}
environment:
- SERVER_SECRET=${SECRET}
@@ -171,8 +160,7 @@ services:
restart: unless-stopped
stats:
- networks:
- - dokploy-network
+
image: hardcoreeng/stats:${HULY_VERSION}
environment:
- PORT=4900
diff --git a/apps/dokploy/templates/immich/docker-compose.yml b/apps/dokploy/templates/immich/docker-compose.yml
index 2a9fb00be..743f70acf 100644
--- a/apps/dokploy/templates/immich/docker-compose.yml
+++ b/apps/dokploy/templates/immich/docker-compose.yml
@@ -3,8 +3,7 @@ version: "3.9"
services:
immich-server:
image: ghcr.io/immich-app/immich-server:v1.121.0
- networks:
- - dokploy-network
+
volumes:
- immich-library:/usr/src/app/upload
- /etc/localtime:/etc/localtime:ro
@@ -38,8 +37,7 @@ services:
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:v1.121.0
- networks:
- - dokploy-network
+
volumes:
- immich-model-cache:/cache
environment:
@@ -55,8 +53,7 @@ services:
immich-redis:
image: redis:6.2-alpine
- networks:
- - dokploy-network
+
volumes:
- immich-redis-data:/data
healthcheck:
@@ -68,8 +65,7 @@ services:
immich-database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0
- networks:
- - dokploy-network
+
volumes:
- immich-postgres:/var/lib/postgresql/data
environment:
diff --git a/apps/dokploy/templates/immich/index.ts b/apps/dokploy/templates/immich/index.ts
index b1b11afb1..4beca87da 100644
--- a/apps/dokploy/templates/immich/index.ts
+++ b/apps/dokploy/templates/immich/index.ts
@@ -11,7 +11,7 @@ export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const dbPassword = generatePassword();
const dbUser = "immich";
- const appSecret = generateBase64(32);
+ const _appSecret = generateBase64(32);
const domains: DomainSchema[] = [
{
diff --git a/apps/dokploy/templates/infisical/docker-compose.yml b/apps/dokploy/templates/infisical/docker-compose.yml
index 3baca9265..7566c8980 100644
--- a/apps/dokploy/templates/infisical/docker-compose.yml
+++ b/apps/dokploy/templates/infisical/docker-compose.yml
@@ -19,8 +19,7 @@ services:
- SMTP_SECURE=true
command: npm run migration:latest
pull_policy: always
- networks:
- - dokploy-network
+
backend:
restart: unless-stopped
@@ -46,8 +45,7 @@ services:
- SMTP_USERNAME
- SMTP_PASSWORD
- SMTP_SECURE=true
- networks:
- - dokploy-network
+
redis:
image: redis:7.4.1
@@ -55,8 +53,7 @@ services:
restart: always
environment:
- ALLOW_EMPTY_PASSWORD=yes
- networks:
- - dokploy-network
+
volumes:
- redis_infisical_data:/data
@@ -69,8 +66,7 @@ services:
- POSTGRES_DB
volumes:
- pg_infisical_data:/var/lib/postgresql/data
- networks:
- - dokploy-network
+
healthcheck:
test: "pg_isready --username=${POSTGRES_USER} && psql --username=${POSTGRES_USER} --list"
interval: 5s
diff --git a/apps/dokploy/templates/invoiceshelf/docker-compose.yml b/apps/dokploy/templates/invoiceshelf/docker-compose.yml
index 5afdd1526..ef47f1c04 100644
--- a/apps/dokploy/templates/invoiceshelf/docker-compose.yml
+++ b/apps/dokploy/templates/invoiceshelf/docker-compose.yml
@@ -3,8 +3,7 @@ version: "3.8"
services:
invoiceshelf-postgres:
image: postgres:15
- networks:
- - dokploy-network
+
volumes:
- invoiceshelf-postgres-data:/var/lib/postgresql/data
environment:
@@ -19,8 +18,7 @@ services:
invoiceshelf-app:
image: invoiceshelf/invoiceshelf:latest
- networks:
- - dokploy-network
+
volumes:
- invoiceshelf-app-data:/data
- invoiceshelf-app-conf:/conf
diff --git a/apps/dokploy/templates/kimai/docker-compose.yml b/apps/dokploy/templates/kimai/docker-compose.yml
index 6a04b3b92..253ecb004 100644
--- a/apps/dokploy/templates/kimai/docker-compose.yml
+++ b/apps/dokploy/templates/kimai/docker-compose.yml
@@ -16,8 +16,7 @@ services:
depends_on:
db:
condition: service_healthy
- networks:
- - dokploy-network
+
db:
image: mariadb:10.11
restart: unless-stopped
@@ -39,8 +38,7 @@ services:
timeout: 5s
retries: 5
start_period: 30s
- networks:
- - dokploy-network
+
networks:
dokploy-network:
diff --git a/apps/dokploy/templates/langflow/docker-compose.yml b/apps/dokploy/templates/langflow/docker-compose.yml
index 75bb73dd1..a96282868 100644
--- a/apps/dokploy/templates/langflow/docker-compose.yml
+++ b/apps/dokploy/templates/langflow/docker-compose.yml
@@ -12,8 +12,7 @@ services:
# This variable defines where the logs, file storage, monitor data and secret keys are stored.
volumes:
- langflow-data:/app/langflow
- networks:
- - dokploy-network
+
postgres-langflow:
image: postgres:16
@@ -25,8 +24,7 @@ services:
- 5432
volumes:
- langflow-postgres:/var/lib/postgresql/data
- networks:
- - dokploy-network
+
volumes:
langflow-postgres:
diff --git a/apps/dokploy/templates/linkwarden/docker-compose.yml b/apps/dokploy/templates/linkwarden/docker-compose.yml
new file mode 100644
index 000000000..05ffb8a0a
--- /dev/null
+++ b/apps/dokploy/templates/linkwarden/docker-compose.yml
@@ -0,0 +1,40 @@
+services:
+ linkwarden:
+ environment:
+ - NEXTAUTH_SECRET
+ - NEXTAUTH_URL
+ - DATABASE_URL=postgresql://linkwarden:${POSTGRES_PASSWORD}@postgres:5432/linkwarden
+ restart: unless-stopped
+ image: ghcr.io/linkwarden/linkwarden:v2.9.3
+ ports:
+ - 3000
+ volumes:
+ - linkwarden-data:/data/data
+ depends_on:
+ - postgres
+ healthcheck:
+ test: curl --fail http://localhost:3000 || exit 1
+ interval: 60s
+ retries: 2
+ start_period: 60s
+ timeout: 15s
+
+ postgres:
+ image: postgres:17-alpine
+ restart: unless-stopped
+ user: postgres
+ environment:
+ POSTGRES_USER: linkwarden
+ POSTGRES_DB: linkwarden
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ volumes:
+ - postgres-data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready"]
+ interval: 10s
+ timeout: 5s
+ retries: 5
+
+volumes:
+ linkwarden-data:
+ postgres-data:
diff --git a/apps/dokploy/templates/linkwarden/index.ts b/apps/dokploy/templates/linkwarden/index.ts
new file mode 100644
index 000000000..860250356
--- /dev/null
+++ b/apps/dokploy/templates/linkwarden/index.ts
@@ -0,0 +1,33 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const postgresPassword = generatePassword();
+ const nextSecret = generateBase64(32);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "linkwarden",
+ },
+ ];
+
+ const envs = [
+ `POSTGRES_PASSWORD=${postgresPassword}`,
+ `NEXTAUTH_SECRET=${nextSecret}`,
+ `NEXTAUTH_URL=http://${mainDomain}/api/v1/auth`,
+ ];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/listmonk/docker-compose.yml b/apps/dokploy/templates/listmonk/docker-compose.yml
index 725d0a098..d9da8b50e 100644
--- a/apps/dokploy/templates/listmonk/docker-compose.yml
+++ b/apps/dokploy/templates/listmonk/docker-compose.yml
@@ -1,10 +1,9 @@
services:
db:
- image: postgres:13
+ image: postgres:17-alpine
ports:
- 5432
- networks:
- - dokploy-network
+
environment:
- POSTGRES_PASSWORD=listmonk
- POSTGRES_USER=listmonk
@@ -19,9 +18,8 @@ services:
- listmonk-data:/var/lib/postgresql/data
setup:
- image: listmonk/listmonk:v3.0.0
- networks:
- - dokploy-network
+ image: listmonk/listmonk:v4.1.0
+
volumes:
- ../files/config.toml:/listmonk/config.toml
depends_on:
@@ -35,7 +33,7 @@ services:
app:
restart: unless-stopped
- image: listmonk/listmonk:v3.0.0
+ image: listmonk/listmonk:v4.1.0
environment:
- TZ=Etc/UTC
depends_on:
@@ -43,7 +41,9 @@ services:
- setup
volumes:
- ../files/config.toml:/listmonk/config.toml
+ - listmonk-uploads:/listmonk/uploads
volumes:
+ listmonk-uploads:
listmonk-data:
driver: local
diff --git a/apps/dokploy/templates/listmonk/index.ts b/apps/dokploy/templates/listmonk/index.ts
index 725659ca4..2a25efcaa 100644
--- a/apps/dokploy/templates/listmonk/index.ts
+++ b/apps/dokploy/templates/listmonk/index.ts
@@ -2,13 +2,11 @@ import {
type DomainSchema,
type Schema,
type Template,
- generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const randomDomain = generateRandomDomain(schema);
- const adminPassword = generatePassword(32);
const domains: DomainSchema[] = [
{
@@ -19,7 +17,7 @@ export function generate(schema: Schema): Template {
];
const envs = [
- `# login with admin:${adminPassword}`,
+ "# visit the page to setup your super admin user",
"# check config.toml in Advanced / Volumes for more options",
];
@@ -29,9 +27,6 @@ export function generate(schema: Schema): Template {
content: `[app]
address = "0.0.0.0:9000"
-admin_username = "admin"
-admin_password = "${adminPassword}"
-
[db]
host = "db"
port = 5432
diff --git a/apps/dokploy/templates/logto/docker-compose.yml b/apps/dokploy/templates/logto/docker-compose.yml
index e1f7c46aa..6f2b920a0 100644
--- a/apps/dokploy/templates/logto/docker-compose.yml
+++ b/apps/dokploy/templates/logto/docker-compose.yml
@@ -8,8 +8,7 @@ services:
ports:
- 3001
- 3002
- networks:
- - dokploy-network
+
environment:
TRUST_PROXY_HEADER: 1
DB_URL: postgres://logto:${LOGTO_POSTGRES_PASSWORD}@postgres:5432/logto
@@ -20,8 +19,7 @@ services:
postgres:
image: postgres:17-alpine
user: postgres
- networks:
- - dokploy-network
+
environment:
POSTGRES_USER: logto
POSTGRES_PASSWORD: ${LOGTO_POSTGRES_PASSWORD}
diff --git a/apps/dokploy/templates/mailpit/docker-compose.yml b/apps/dokploy/templates/mailpit/docker-compose.yml
new file mode 100644
index 000000000..d0dbdb8ec
--- /dev/null
+++ b/apps/dokploy/templates/mailpit/docker-compose.yml
@@ -0,0 +1,25 @@
+services:
+ mailpit:
+ image: axllent/mailpit:v1.22.3
+ restart: unless-stopped
+ ports:
+ - '1025:1025'
+ volumes:
+ - 'mailpit-data:/data'
+ environment:
+ - MP_SMTP_AUTH_ALLOW_INSECURE=true
+ - MP_MAX_MESSAGES=5000
+ - MP_DATABASE=/data/mailpit.db
+ - MP_UI_AUTH=${MP_UI_AUTH}
+ - MP_SMTP_AUTH=${MP_SMTP_AUTH}
+ healthcheck:
+ test:
+ - CMD
+ - /mailpit
+ - readyz
+ interval: 5s
+ timeout: 20s
+ retries: 10
+
+volumes:
+ mailpit-data:
\ No newline at end of file
diff --git a/apps/dokploy/templates/mailpit/index.ts b/apps/dokploy/templates/mailpit/index.ts
new file mode 100644
index 000000000..25f18f7e6
--- /dev/null
+++ b/apps/dokploy/templates/mailpit/index.ts
@@ -0,0 +1,31 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 8025,
+ serviceName: "mailpit",
+ },
+ ];
+
+ const defaultPassword = generatePassword();
+
+ const envs = [
+ "# Uncomment below if you want basic auth on UI and SMTP",
+ `#MP_UI_AUTH=mailpit:${defaultPassword}`,
+ `#MP_SMTP_AUTH=mailpit:${defaultPassword}`,
+ ];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/maybe/docker-compose.yml b/apps/dokploy/templates/maybe/docker-compose.yml
new file mode 100644
index 000000000..db529e0a7
--- /dev/null
+++ b/apps/dokploy/templates/maybe/docker-compose.yml
@@ -0,0 +1,36 @@
+services:
+ app:
+ image: ghcr.io/maybe-finance/maybe:sha-68c570eed8810fd59b5b33cca51bbad5eabb4cb4
+ restart: unless-stopped
+ volumes:
+ - ../files/uploads:/app/uploads
+ environment:
+ DATABASE_URL: postgresql://maybe:maybe@db:5432/maybe
+ SECRET_KEY_BASE: ${SECRET_KEY_BASE}
+ SELF_HOSTED: true
+ SYNTH_API_KEY: ${SYNTH_API_KEY}
+ RAILS_FORCE_SSL: "false"
+ RAILS_ASSUME_SSL: "false"
+ GOOD_JOB_EXECUTION_MODE: async
+ depends_on:
+ db:
+ condition: service_healthy
+
+ db:
+ image: postgres:16
+ restart: always
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
+ interval: 5s
+ timeout: 5s
+ retries: 5
+
+ volumes:
+ - db-data:/var/lib/postgresql/data
+ environment:
+ POSTGRES_DB: maybe
+ POSTGRES_USER: maybe
+ POSTGRES_PASSWORD: maybe
+
+volumes:
+ db-data:
diff --git a/apps/dokploy/templates/maybe/index.ts b/apps/dokploy/templates/maybe/index.ts
new file mode 100644
index 000000000..5eaf7a811
--- /dev/null
+++ b/apps/dokploy/templates/maybe/index.ts
@@ -0,0 +1,43 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const secretKeyBase = generateBase64(64);
+ const synthApiKey = generateBase64(32);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "app",
+ },
+ ];
+
+ const envs = [
+ `SECRET_KEY_BASE=${secretKeyBase}`,
+ "SELF_HOSTED=true",
+ `SYNTH_API_KEY=${synthApiKey}`,
+ "RAILS_FORCE_SSL=false",
+ "RAILS_ASSUME_SSL=false",
+ "GOOD_JOB_EXECUTION_MODE=async",
+ ];
+
+ const mounts: Template["mounts"] = [
+ {
+ filePath: "./uploads",
+ content: "This is where user uploads will be stored",
+ },
+ ];
+
+ return {
+ envs,
+ mounts,
+ domains,
+ };
+}
diff --git a/apps/dokploy/templates/metabase/docker-compose.yml b/apps/dokploy/templates/metabase/docker-compose.yml
index 4dca4d015..43a03987d 100644
--- a/apps/dokploy/templates/metabase/docker-compose.yml
+++ b/apps/dokploy/templates/metabase/docker-compose.yml
@@ -22,5 +22,4 @@ services:
POSTGRES_USER: metabase
POSTGRES_DB: metabaseappdb
POSTGRES_PASSWORD: mysecretpassword
- networks:
- - dokploy-network
+
diff --git a/apps/dokploy/templates/nextcloud-aio/docker-compose.yml b/apps/dokploy/templates/nextcloud-aio/docker-compose.yml
index e8381d2b0..1e6d00fe3 100644
--- a/apps/dokploy/templates/nextcloud-aio/docker-compose.yml
+++ b/apps/dokploy/templates/nextcloud-aio/docker-compose.yml
@@ -2,8 +2,7 @@ services:
nextcloud:
image: nextcloud:30.0.2
restart: always
- networks:
- - dokploy-network
+
ports:
- 80
volumes:
@@ -19,8 +18,7 @@ services:
nextcloud_db:
image: mariadb
restart: always
- networks:
- - dokploy-network
+
volumes:
- nextcloud_db_data:/var/lib/mysql
environment:
diff --git a/apps/dokploy/templates/nocodb/docker-compose.yml b/apps/dokploy/templates/nocodb/docker-compose.yml
index 3d5c9ee7a..7c4fd1e95 100644
--- a/apps/dokploy/templates/nocodb/docker-compose.yml
+++ b/apps/dokploy/templates/nocodb/docker-compose.yml
@@ -13,8 +13,7 @@ services:
root_db:
image: postgres:17
restart: always
- networks:
- - dokploy-network
+
environment:
POSTGRES_DB: root_db
POSTGRES_PASSWORD: password
diff --git a/apps/dokploy/templates/odoo/docker-compose.yml b/apps/dokploy/templates/odoo/docker-compose.yml
index 80b34f0c0..7c1c7d3ce 100644
--- a/apps/dokploy/templates/odoo/docker-compose.yml
+++ b/apps/dokploy/templates/odoo/docker-compose.yml
@@ -15,8 +15,7 @@ services:
db:
image: postgres:13
- networks:
- - dokploy-network
+
environment:
- POSTGRES_DB=postgres
- POSTGRES_USER=odoo
diff --git a/apps/dokploy/templates/open-webui/docker-compose.yml b/apps/dokploy/templates/open-webui/docker-compose.yml
index d396dacc0..ee179721a 100644
--- a/apps/dokploy/templates/open-webui/docker-compose.yml
+++ b/apps/dokploy/templates/open-webui/docker-compose.yml
@@ -3,8 +3,7 @@ services:
ollama:
volumes:
- ollama:/root/.ollama
- networks:
- - dokploy-network
+
pull_policy: always
tty: true
restart: unless-stopped
diff --git a/apps/dokploy/templates/outline/docker-compose.yml b/apps/dokploy/templates/outline/docker-compose.yml
new file mode 100644
index 000000000..aaf98ac0b
--- /dev/null
+++ b/apps/dokploy/templates/outline/docker-compose.yml
@@ -0,0 +1,57 @@
+services:
+ outline:
+ image: outlinewiki/outline:0.82.0
+ restart: always
+ depends_on:
+ - postgres
+ - redis
+ - dex
+ ports:
+ - 3000
+ environment:
+ NODE_ENV: production
+ URL: ${URL}
+ FORCE_HTTPS: 'false'
+ SECRET_KEY: ${SECRET_KEY}
+ UTILS_SECRET: ${UTILS_SECRET}
+ DATABASE_URL: postgres://outline:${POSTGRES_PASSWORD}@postgres:5432/outline
+ PGSSLMODE: disable
+ REDIS_URL: redis://redis:6379
+ OIDC_CLIENT_ID: outline
+ OIDC_CLIENT_SECRET: ${CLIENT_SECRET}
+ OIDC_AUTH_URI: ${DEX_URL}/auth
+ OIDC_TOKEN_URI: ${DEX_URL}/token
+ OIDC_USERINFO_URI: ${DEX_URL}/userinfo
+
+ dex:
+ image: ghcr.io/dexidp/dex:v2.37.0
+ restart: always
+ volumes:
+ - ../files/etc/dex/config.yaml:/etc/dex/config.yaml
+ command:
+ - dex
+ - serve
+ - /etc/dex/config.yaml
+ ports:
+ - 5556
+
+ postgres:
+ image: postgres:15
+ restart: always
+ environment:
+ POSTGRES_DB: outline
+ POSTGRES_USER: outline
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ volumes:
+ - postgres_data-test-outline-khufpx:/var/lib/postgresql/data
+
+ redis:
+ image: redis:latest
+ restart: always
+ command: redis-server --appendonly yes
+ volumes:
+ - redis_data-test-outline-khufpx:/data
+
+volumes:
+ postgres_data-test-outline-khufpx:
+ redis_data-test-outline-khufpx:
\ No newline at end of file
diff --git a/apps/dokploy/templates/outline/index.ts b/apps/dokploy/templates/outline/index.ts
new file mode 100644
index 000000000..8431e5687
--- /dev/null
+++ b/apps/dokploy/templates/outline/index.ts
@@ -0,0 +1,90 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateBase64,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+ const dexDomain = generateRandomDomain(schema);
+ const SECRET_KEY = generateBase64(32);
+ const UTILS_SECRET = generateBase64(32);
+ const CLIENT_SECRET = generateBase64(32);
+ const POSTGRES_PASSWORD = generatePassword();
+
+ const mainURL = `http://${mainDomain}`;
+ const dexURL = `http://${dexDomain}`;
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 3000,
+ serviceName: "outline",
+ },
+ {
+ host: dexDomain,
+ port: 5556,
+ serviceName: "dex",
+ },
+ ];
+
+ const mounts: Template["mounts"] = [
+ {
+ filePath: "/etc/dex/config.yaml",
+ content: `issuer: ${dexURL}
+
+web:
+ http: 0.0.0.0:5556
+
+storage:
+ type: memory
+
+enablePasswordDB: true
+
+frontend:
+ issuer: Outline
+
+logger:
+ level: debug
+
+staticPasswords:
+ - email: "admin@example.com"
+ # bcrypt hash of the string "password": $(echo password | htpasswd -BinC 10 admin | cut -d: -f2)
+ hash: "$2y$10$jsRWHw54uxTUIfhjgUrB9u8HSzPk7TUuQri9sXZrKzRXcScvwYor."
+ username: "admin"
+ userID: "1"
+
+
+oauth2:
+ skipApprovalScreen: true
+ alwaysShowLoginScreen: false
+ passwordConnector: local
+
+staticClients:
+ - id: "outline"
+ redirectURIs:
+ - ${mainURL}/auth/oidc.callback
+ name: "Outline"
+ secret: "${CLIENT_SECRET}"`,
+ },
+ ];
+
+ const envs = [
+ `URL=${mainURL}`,
+ `DEX_URL=${dexURL}`,
+ `DOMAIN_NAME=${mainDomain}`,
+ `POSTGRES_PASSWORD=${POSTGRES_PASSWORD}`,
+ `SECRET_KEY=${SECRET_KEY}`,
+ `UTILS_SECRET=${UTILS_SECRET}`,
+ `CLIENT_SECRET=${CLIENT_SECRET}`,
+ ];
+
+ return {
+ domains,
+ envs,
+ mounts,
+ };
+}
diff --git a/apps/dokploy/templates/penpot/docker-compose.yml b/apps/dokploy/templates/penpot/docker-compose.yml
index 55abdb53d..3e0efe915 100644
--- a/apps/dokploy/templates/penpot/docker-compose.yml
+++ b/apps/dokploy/templates/penpot/docker-compose.yml
@@ -46,8 +46,7 @@ services:
- penpot-backend
- penpot-exporter
- networks:
- - dokploy-network
+
environment:
PENPOT_FLAGS: disable-email-verification enable-smtp enable-prepl-server disable-secure-session-cookies
@@ -63,8 +62,7 @@ services:
- penpot-postgres
- penpot-redis
- networks:
- - dokploy-network
+
## Configuration envronment variables for the backend
## container.
@@ -143,8 +141,7 @@ services:
penpot-exporter:
image: "penpotapp/exporter:2.3.2"
restart: always
- networks:
- - dokploy-network
+
environment:
# Don't touch it; this uses an internal docker network to
@@ -162,8 +159,7 @@ services:
volumes:
- penpot_postgres_v15:/var/lib/postgresql/data
- networks:
- - dokploy-network
+
environment:
- POSTGRES_INITDB_ARGS=--data-checksums
@@ -174,8 +170,7 @@ services:
penpot-redis:
image: redis:7.2
restart: always
- networks:
- - dokploy-network
+
## A mailcatch service, used as temporal SMTP server. You can access via HTTP to the
## port 1080 for read all emails the penpot platform has sent. Should be only used as a
@@ -188,8 +183,7 @@ services:
- '1025'
ports:
- 1080
- networks:
- - dokploy-network
+
## Example configuration of MiniIO (S3 compatible object storage service); If you don't
## have preference, then just use filesystem, this is here just for the completeness.
diff --git a/apps/dokploy/templates/penpot/index.ts b/apps/dokploy/templates/penpot/index.ts
index f657c698f..a3e90e8ae 100644
--- a/apps/dokploy/templates/penpot/index.ts
+++ b/apps/dokploy/templates/penpot/index.ts
@@ -2,8 +2,6 @@ import {
type DomainSchema,
type Schema,
type Template,
- generateBase64,
- generatePassword,
generateRandomDomain,
} from "../utils";
diff --git a/apps/dokploy/templates/peppermint/docker-compose.yml b/apps/dokploy/templates/peppermint/docker-compose.yml
index 305c5eb97..06fb46c66 100644
--- a/apps/dokploy/templates/peppermint/docker-compose.yml
+++ b/apps/dokploy/templates/peppermint/docker-compose.yml
@@ -4,8 +4,7 @@ services:
peppermint-postgres:
image: postgres:latest
restart: always
- networks:
- - dokploy-network
+
volumes:
- peppermint-postgres-data:/var/lib/postgresql/data
environment:
@@ -21,8 +20,7 @@ services:
peppermint-app:
image: pepperlabs/peppermint:latest
restart: always
- networks:
- - dokploy-network
+
depends_on:
peppermint-postgres:
condition: service_healthy
diff --git a/apps/dokploy/templates/photoprism/docker-compose.yml b/apps/dokploy/templates/photoprism/docker-compose.yml
index 285f2ff86..56793dbd3 100644
--- a/apps/dokploy/templates/photoprism/docker-compose.yml
+++ b/apps/dokploy/templates/photoprism/docker-compose.yml
@@ -7,8 +7,7 @@ services:
security_opt:
- seccomp:unconfined
- apparmor:unconfined
- networks:
- - dokploy-network
+
environment:
PHOTOPRISM_ADMIN_USER: "admin"
PHOTOPRISM_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
@@ -57,8 +56,7 @@ services:
image: mariadb:11
restart: unless-stopped
stop_grace_period: 5s
- networks:
- - dokploy-network
+
security_opt:
- seccomp:unconfined
- apparmor:unconfined
diff --git a/apps/dokploy/templates/photoprism/index.ts b/apps/dokploy/templates/photoprism/index.ts
index d20ac29c8..4a103a624 100644
--- a/apps/dokploy/templates/photoprism/index.ts
+++ b/apps/dokploy/templates/photoprism/index.ts
@@ -2,7 +2,6 @@ import {
type DomainSchema,
type Schema,
type Template,
- generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
diff --git a/apps/dokploy/templates/phpmyadmin/docker-compose.yml b/apps/dokploy/templates/phpmyadmin/docker-compose.yml
index 1f775f09a..91674e87c 100644
--- a/apps/dokploy/templates/phpmyadmin/docker-compose.yml
+++ b/apps/dokploy/templates/phpmyadmin/docker-compose.yml
@@ -10,8 +10,7 @@ services:
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- db_data:/var/lib/mysql
- networks:
- - dokploy-network
+
phpmyadmin:
image: phpmyadmin/phpmyadmin:5.2.1
diff --git a/apps/dokploy/templates/plausible/docker-compose.yml b/apps/dokploy/templates/plausible/docker-compose.yml
index bb267f65c..ad483ecf6 100644
--- a/apps/dokploy/templates/plausible/docker-compose.yml
+++ b/apps/dokploy/templates/plausible/docker-compose.yml
@@ -1,10 +1,8 @@
-version: "3.8"
services:
plausible_db:
image: postgres:16-alpine
restart: always
- networks:
- - dokploy-network
+
volumes:
- db-data:/var/lib/postgresql/data
environment:
@@ -13,8 +11,7 @@ services:
plausible_events_db:
image: clickhouse/clickhouse-server:24.3.3.102-alpine
restart: always
- networks:
- - dokploy-network
+
volumes:
- event-data:/var/lib/clickhouse
- event-logs:/var/log/clickhouse-server
@@ -26,7 +23,7 @@ services:
hard: 262144
plausible:
- image: ghcr.io/plausible/community-edition:v2.1.4
+ image: ghcr.io/plausible/community-edition:v2.1.5
restart: always
command: sh -c "sleep 10 && /entrypoint.sh db createdb && /entrypoint.sh db migrate && /entrypoint.sh run"
depends_on:
diff --git a/apps/dokploy/templates/pocket-id/docker-compose.yml b/apps/dokploy/templates/pocket-id/docker-compose.yml
new file mode 100644
index 000000000..f93851430
--- /dev/null
+++ b/apps/dokploy/templates/pocket-id/docker-compose.yml
@@ -0,0 +1,21 @@
+services:
+ pocket-id:
+ image: ghcr.io/pocket-id/pocket-id:v0.35.1
+ restart: unless-stopped
+ environment:
+ - PUBLIC_UI_CONFIG_DISABLED
+ - PUBLIC_APP_URL
+ - TRUST_PROXY
+ ports:
+ - 80
+ volumes:
+ - pocket-id-data:/app/backend/data
+ healthcheck:
+ test: "curl -f http://localhost/health"
+ interval: 1m30s
+ timeout: 5s
+ retries: 2
+ start_period: 10s
+
+volumes:
+ pocket-id-data:
diff --git a/apps/dokploy/templates/pocket-id/index.ts b/apps/dokploy/templates/pocket-id/index.ts
new file mode 100644
index 000000000..9a9faa2a3
--- /dev/null
+++ b/apps/dokploy/templates/pocket-id/index.ts
@@ -0,0 +1,29 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mainDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: mainDomain,
+ port: 80,
+ serviceName: "pocket-id",
+ },
+ ];
+
+ const envs = [
+ "PUBLIC_UI_CONFIG_DISABLED=false",
+ `PUBLIC_APP_URL=http://${mainDomain}`,
+ "TRUST_PROXY=true",
+ ];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/portainer/docker-compose.yml b/apps/dokploy/templates/portainer/docker-compose.yml
index fa4fe4103..19e67a3e5 100644
--- a/apps/dokploy/templates/portainer/docker-compose.yml
+++ b/apps/dokploy/templates/portainer/docker-compose.yml
@@ -6,8 +6,7 @@ services:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- /var/lib/docker/volumes:/var/lib/docker/volumes
- networks:
- - dokploy-network
+
deploy:
mode: global
diff --git a/apps/dokploy/templates/postiz/docker-compose.yml b/apps/dokploy/templates/postiz/docker-compose.yml
index f842c92d9..cd06e7952 100644
--- a/apps/dokploy/templates/postiz/docker-compose.yml
+++ b/apps/dokploy/templates/postiz/docker-compose.yml
@@ -4,8 +4,7 @@ services:
postiz-app:
image: ghcr.io/gitroomhq/postiz-app:latest
restart: always
- networks:
- - dokploy-network
+
environment:
MAIN_URL: "https://${POSTIZ_HOST}"
FRONTEND_URL: "https://${POSTIZ_HOST}"
@@ -30,8 +29,7 @@ services:
postiz-postgres:
image: postgres:17-alpine
restart: always
- networks:
- - dokploy-network
+
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USER}
@@ -47,8 +45,7 @@ services:
postiz-redis:
image: redis:7.2
restart: always
- networks:
- - dokploy-network
+
healthcheck:
test: redis-cli ping
interval: 10s
diff --git a/apps/dokploy/templates/registry/docker-compose.yml b/apps/dokploy/templates/registry/docker-compose.yml
new file mode 100644
index 000000000..08c5c3688
--- /dev/null
+++ b/apps/dokploy/templates/registry/docker-compose.yml
@@ -0,0 +1,19 @@
+services:
+ registry:
+ restart: always
+ image: registry:2
+ ports:
+ - 5000
+ volumes:
+ - ../files/auth/registry.password:/auth/registry.password
+ - registry-data:/var/lib/registry
+ environment:
+ REGISTRY_STORAGE_DELETE_ENABLED: true
+ REGISTRY_HEALTH_STORAGEDRIVER_ENABLED: false
+ REGISTRY_HTTP_SECRET: ${REGISTRY_HTTP_SECRET}
+ REGISTRY_AUTH: htpasswd
+ REGISTRY_AUTH_HTPASSWD_REALM: Registry Realm
+ REGISTRY_AUTH_HTPASSWD_PATH: /auth/registry.password
+
+volumes:
+ registry-data:
\ No newline at end of file
diff --git a/apps/dokploy/templates/registry/index.ts b/apps/dokploy/templates/registry/index.ts
new file mode 100644
index 000000000..81965e6e2
--- /dev/null
+++ b/apps/dokploy/templates/registry/index.ts
@@ -0,0 +1,35 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 5000,
+ serviceName: "registry",
+ },
+ ];
+
+ const registryHttpSecret = generatePassword(30);
+
+ const envs = [`REGISTRY_HTTP_SECRET=${registryHttpSecret}`];
+
+ const mounts: Template["mounts"] = [
+ {
+ filePath: "/auth/registry.password",
+ content:
+ "# from: docker run --rm --entrypoint htpasswd httpd:2 -Bbn docker password\ndocker:$2y$10$qWZoWev/u5PV7WneFoRAMuoGpRcAQOgUuIIdLnU7pJXogrBSY23/2\n",
+ },
+ ];
+
+ return {
+ domains,
+ envs,
+ mounts,
+ };
+}
diff --git a/apps/dokploy/templates/rocketchat/docker-compose.yml b/apps/dokploy/templates/rocketchat/docker-compose.yml
index 751bd845c..5119f5a4e 100644
--- a/apps/dokploy/templates/rocketchat/docker-compose.yml
+++ b/apps/dokploy/templates/rocketchat/docker-compose.yml
@@ -28,8 +28,7 @@ services:
MONGODB_ADVERTISED_HOSTNAME: mongodb
MONGODB_ENABLE_JOURNAL: true
ALLOW_EMPTY_PASSWORD: yes
- networks:
- - dokploy-network
+
volumes:
mongodb_data: { driver: local }
diff --git a/apps/dokploy/templates/roundcube/docker-compose.yml b/apps/dokploy/templates/roundcube/docker-compose.yml
index 440f907dd..e5ba4a8b1 100644
--- a/apps/dokploy/templates/roundcube/docker-compose.yml
+++ b/apps/dokploy/templates/roundcube/docker-compose.yml
@@ -9,8 +9,7 @@ services:
- ROUNDCUBEMAIL_SKIN=elastic
- ROUNDCUBEMAIL_DEFAULT_HOST=${DEFAULT_HOST}
- ROUNDCUBEMAIL_SMTP_SERVER=${SMTP_SERVER}
- networks:
- - dokploy-network
+
networks:
dokploy-network:
diff --git a/apps/dokploy/templates/ryot/docker-compose.yml b/apps/dokploy/templates/ryot/docker-compose.yml
index 1fcd80ed1..09a727071 100644
--- a/apps/dokploy/templates/ryot/docker-compose.yml
+++ b/apps/dokploy/templates/ryot/docker-compose.yml
@@ -3,8 +3,7 @@ version: '3.7'
services:
ryot-app:
image: ignisda/ryot:v7.10
- networks:
- - dokploy-network
+
environment:
- DATABASE_URL=postgres://postgres:${POSTGRES_PASSWORD}@ryot-db:5432/postgres
- SERVER_ADMIN_ACCESS_TOKEN=${ADMIN_ACCESS_TOKEN}
@@ -19,8 +18,7 @@ services:
ryot-db:
image: postgres:16-alpine
- networks:
- - dokploy-network
+
volumes:
- ryot-postgres-data:/var/lib/postgresql/data
environment:
diff --git a/apps/dokploy/templates/shlink/docker-compose.yml b/apps/dokploy/templates/shlink/docker-compose.yml
new file mode 100644
index 000000000..6d15a26d8
--- /dev/null
+++ b/apps/dokploy/templates/shlink/docker-compose.yml
@@ -0,0 +1,29 @@
+services:
+ shlink:
+ image: shlinkio/shlink:stable
+ environment:
+ - INITIAL_API_KEY=${INITIAL_API_KEY}
+ - DEFAULT_DOMAIN=${DEFAULT_DOMAIN}
+ # Note: you should also update SHLINK_SERVER_URL in the shlink-web service.
+ - IS_HTTPS_ENABLED=false
+ volumes:
+ - shlink-data:/etc/shlink/data
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://127.0.0.1:8080/rest/v3/health"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+ shlink-web:
+ image: shlinkio/shlink-web-client
+ environment:
+ - SHLINK_SERVER_API_KEY=${INITIAL_API_KEY}
+ # Note: if you've set IS_HTTPS_ENABLED=true, change http to https.
+ - SHLINK_SERVER_URL=http://${DEFAULT_DOMAIN}
+ healthcheck:
+ test: ["CMD", "curl", "-f", "http://127.0.0.1:8080"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+
+volumes:
+ shlink-data:
diff --git a/apps/dokploy/templates/shlink/index.ts b/apps/dokploy/templates/shlink/index.ts
new file mode 100644
index 000000000..1e456e1c2
--- /dev/null
+++ b/apps/dokploy/templates/shlink/index.ts
@@ -0,0 +1,35 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const defaultDomain = generateRandomDomain(schema);
+ const initialApiKey = generatePassword(30);
+
+ const domains: DomainSchema[] = [
+ {
+ host: `web-${defaultDomain}`,
+ port: 8080,
+ serviceName: "shlink-web",
+ },
+ {
+ host: defaultDomain,
+ port: 8080,
+ serviceName: "shlink",
+ },
+ ];
+
+ const envs = [
+ `INITIAL_API_KEY=${initialApiKey}`,
+ `DEFAULT_DOMAIN=${defaultDomain}`,
+ ];
+
+ return {
+ envs,
+ domains,
+ };
+}
diff --git a/apps/dokploy/templates/slash/docker-compose.yml b/apps/dokploy/templates/slash/docker-compose.yml
index 75afc4783..ee6cdf895 100644
--- a/apps/dokploy/templates/slash/docker-compose.yml
+++ b/apps/dokploy/templates/slash/docker-compose.yml
@@ -3,8 +3,7 @@ version: "3.8"
services:
slash-app:
image: yourselfhosted/slash:latest
- networks:
- - dokploy-network
+
volumes:
- slash-app-data:/var/opt/slash
environment:
@@ -17,8 +16,7 @@ services:
slash-postgres:
image: postgres:16-alpine
- networks:
- - dokploy-network
+
volumes:
- slash-postgres-data:/var/lib/postgresql/data
environment:
diff --git a/apps/dokploy/templates/spacedrive/docker-compose.yml b/apps/dokploy/templates/spacedrive/docker-compose.yml
new file mode 100644
index 000000000..b98d55abf
--- /dev/null
+++ b/apps/dokploy/templates/spacedrive/docker-compose.yml
@@ -0,0 +1,9 @@
+services:
+ server:
+ image: ghcr.io/spacedriveapp/spacedrive/server:latest
+ ports:
+ - 8080
+ environment:
+ - SD_AUTH=${SD_USERNAME}:${SD_PASSWORD}
+ volumes:
+ - /var/spacedrive:/var/spacedrive
diff --git a/apps/dokploy/templates/spacedrive/index.ts b/apps/dokploy/templates/spacedrive/index.ts
new file mode 100644
index 000000000..15db4b198
--- /dev/null
+++ b/apps/dokploy/templates/spacedrive/index.ts
@@ -0,0 +1,28 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const randomDomain = generateRandomDomain(schema);
+ const secretKey = generatePassword();
+ const randomUsername = "admin"; // Default username
+
+ const domains: DomainSchema[] = [
+ {
+ host: randomDomain,
+ port: 8080,
+ serviceName: "server",
+ },
+ ];
+
+ const envs = [`SD_USERNAME=${randomUsername}`, `SD_PASSWORD=${secretKey}`];
+
+ return {
+ envs,
+ domains,
+ };
+}
diff --git a/apps/dokploy/templates/supabase/docker-compose.yml b/apps/dokploy/templates/supabase/docker-compose.yml
index e1e187fde..89339736f 100644
--- a/apps/dokploy/templates/supabase/docker-compose.yml
+++ b/apps/dokploy/templates/supabase/docker-compose.yml
@@ -11,8 +11,7 @@ services:
studio:
container_name: supabase-studio
image: supabase/studio:20240729-ce42139
- networks:
- - dokploy-network
+
restart: unless-stopped
healthcheck:
test:
@@ -53,8 +52,7 @@ services:
container_name: supabase-kong
image: kong:2.8.1
restart: unless-stopped
- networks:
- - dokploy-network
+
# https://unix.stackexchange.com/a/294837
entrypoint: bash -c 'eval "echo \"$$(cat ~/temp.yml)\"" > ~/kong.yml && /docker-entrypoint.sh kong docker-start'
#ports:
@@ -85,8 +83,7 @@ services:
auth:
container_name: supabase-auth
image: supabase/gotrue:v2.158.1
- networks:
- - dokploy-network
+
depends_on:
db:
# Disable this if you are using an external Postgres database
@@ -157,8 +154,7 @@ services:
rest:
container_name: supabase-rest
image: postgrest/postgrest:v12.2.0
- networks:
- - dokploy-network
+
depends_on:
db:
# Disable this if you are using an external Postgres database
@@ -180,8 +176,7 @@ services:
# This container name looks inconsistent but is correct because realtime constructs tenant id by parsing the subdomain
container_name: realtime-dev.supabase-realtime
image: supabase/realtime:v2.30.23
- networks:
- - dokploy-network
+
depends_on:
db:
# Disable this if you are using an external Postgres database
@@ -226,8 +221,7 @@ services:
storage:
container_name: supabase-storage
image: supabase/storage-api:v1.0.6
- networks:
- - dokploy-network
+
depends_on:
db:
# Disable this if you are using an external Postgres database
@@ -271,8 +265,7 @@ services:
imgproxy:
container_name: supabase-imgproxy
image: darthsim/imgproxy:v3.8.0
- networks:
- - dokploy-network
+
healthcheck:
test: ["CMD", "imgproxy", "health"]
timeout: 5s
@@ -289,8 +282,7 @@ services:
meta:
container_name: supabase-meta
image: supabase/postgres-meta:v0.83.2
- networks:
- - dokploy-network
+
depends_on:
db:
# Disable this if you are using an external Postgres database
@@ -310,8 +302,7 @@ services:
container_name: supabase-edge-functions
image: supabase/edge-runtime:v1.56.0
restart: unless-stopped
- networks:
- - dokploy-network
+
depends_on:
analytics:
condition: service_healthy
@@ -333,8 +324,7 @@ services:
analytics:
container_name: supabase-analytics
image: supabase/logflare:1.4.0
- networks:
- - dokploy-network
+
healthcheck:
test: ["CMD", "curl", "http://localhost:4000/health"]
timeout: 5s
@@ -380,8 +370,7 @@ services:
db:
container_name: supabase-db
image: supabase/postgres:15.1.1.78
- networks:
- - dokploy-network
+
healthcheck:
test: pg_isready -U postgres -h localhost
interval: 5s
@@ -430,8 +419,7 @@ services:
vector:
container_name: supabase-vector
image: timberio/vector:0.28.1-alpine
- networks:
- - dokploy-network
+
healthcheck:
test:
[
diff --git a/apps/dokploy/templates/superset/docker-compose.yml b/apps/dokploy/templates/superset/docker-compose.yml
new file mode 100644
index 000000000..b73bf55e5
--- /dev/null
+++ b/apps/dokploy/templates/superset/docker-compose.yml
@@ -0,0 +1,87 @@
+# This is an UNOFFICIAL production docker image build for Superset:
+# - https://github.com/amancevice/docker-superset
+
+
+# ## SETUP INSTRUCTIONS
+#
+# After deploying this image, you will need to run one of the two
+# commands below in a terminal within the superset container:
+# $ superset-demo # Initialise database + load demo charts/datasets
+# $ superset-init # Initialise database only
+#
+# You will be prompted to enter the credentials for the admin user.
+
+
+# ## NETWORK INSTRUCTIONS
+#
+# If you want to connect superset with other internal databases managed by
+# Dokploy (on dokploy-network) using internal hostnames, you will need to
+# uncomment the `networks` section, both for the superset container and
+# at the very bottom of this docker-compose template.
+#
+# Note that the `superset` service name/hostname will not be unique on the
+# global `dokploy-network`. If you plan to:
+#
+# 1. deploy a second instance of superset on dokploy-network, and
+# 2. have other containers on dokploy-network utilise the second instance's
+# Superset API (https://superset.apache.org/docs/api)
+#
+# Please change the service name of the second instance.
+
+services:
+ superset:
+ image: amancevice/superset
+ restart: always
+ #networks:
+ # - dokploy-network
+ depends_on:
+ - superset_postgres
+ - superset_redis
+ volumes:
+ # This superset_config.py can be edited in Dokploy's UI Advanced -> Volume Mount
+ - ../files/superset/superset_config.py:/etc/superset/superset_config.py
+ environment:
+ SECRET_KEY: ${SECRET_KEY}
+ MAPBOX_API_KEY: ${MAPBOX_API_KEY}
+ POSTGRES_USER: ${POSTGRES_USER}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ POSTGRES_DB: ${POSTGRES_DB}
+ REDIS_PASSWORD: ${REDIS_PASSWORD}
+ # Ensure the hosts matches your service names below.
+ POSTGRES_HOST: superset_postgres
+ REDIS_HOST: superset_redis
+
+ superset_postgres:
+ image: postgres
+ restart: always
+ environment:
+ POSTGRES_USER: ${POSTGRES_USER}
+ POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
+ POSTGRES_DB: ${POSTGRES_DB}
+ volumes:
+ - superset_postgres_data:/var/lib/postgresql/data
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+
+superset_redis:
+ image: redis
+ restart: always
+ volumes:
+ - superset_redis_data:/data
+ command: redis-server --requirepass ${REDIS_PASSWORD}
+ healthcheck:
+ test: ["CMD-SHELL", "pg_isready -U $${POSTGRES_USER} -d $${POSTGRES_DB}"]
+ interval: 30s
+ timeout: 10s
+ retries: 3
+
+#networks:
+# dokploy-network:
+# external: true
+
+volumes:
+ superset_postgres_data:
+ superset_redis_data:
diff --git a/apps/dokploy/templates/superset/index.ts b/apps/dokploy/templates/superset/index.ts
new file mode 100644
index 000000000..954fc9710
--- /dev/null
+++ b/apps/dokploy/templates/superset/index.ts
@@ -0,0 +1,77 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generatePassword,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const mapboxApiKey = "";
+ const secretKey = generatePassword(30);
+ const postgresDb = "superset";
+ const postgresUser = "superset";
+ const postgresPassword = generatePassword(30);
+ const redisPassword = generatePassword(30);
+
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 8088,
+ serviceName: "superset",
+ },
+ ];
+
+ const envs = [
+ `SECRET_KEY=${secretKey}`,
+ `MAPBOX_API_KEY=${mapboxApiKey}`,
+ `POSTGRES_DB=${postgresDb}`,
+ `POSTGRES_USER=${postgresUser}`,
+ `POSTGRES_PASSWORD=${postgresPassword}`,
+ `REDIS_PASSWORD=${redisPassword}`,
+ ];
+
+ const mounts: Template["mounts"] = [
+ {
+ filePath: "./superset/superset_config.py",
+ content: `
+"""
+For more configuration options, see:
+- https://superset.apache.org/docs/configuration/configuring-superset
+"""
+
+import os
+
+SECRET_KEY = os.getenv("SECRET_KEY")
+MAPBOX_API_KEY = os.getenv("MAPBOX_API_KEY", "")
+
+CACHE_CONFIG = {
+ "CACHE_TYPE": "RedisCache",
+ "CACHE_DEFAULT_TIMEOUT": 300,
+ "CACHE_KEY_PREFIX": "superset_",
+ "CACHE_REDIS_HOST": "redis",
+ "CACHE_REDIS_PORT": 6379,
+ "CACHE_REDIS_DB": 1,
+ "CACHE_REDIS_URL": f"redis://:{os.getenv('REDIS_PASSWORD')}@{os.getenv('REDIS_HOST')}:6379/1",
+}
+
+FILTER_STATE_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_KEY_PREFIX": "superset_filter_"}
+EXPLORE_FORM_DATA_CACHE_CONFIG = {**CACHE_CONFIG, "CACHE_KEY_PREFIX": "superset_explore_form_"}
+
+SQLALCHEMY_TRACK_MODIFICATIONS = True
+SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://{os.getenv('POSTGRES_USER')}:{os.getenv('POSTGRES_PASSWORD')}@{os.getenv('POSTGRES_HOST')}:5432/{os.getenv('POSTGRES_DB')}"
+
+# Uncomment if you want to load example data (using "superset load_examples") at the
+# same location as your metadata postgresql instance. Otherwise, the default sqlite
+# will be used, which will not persist in volume when restarting superset by default.
+#SQLALCHEMY_EXAMPLES_URI = SQLALCHEMY_DATABASE_URI
+ `.trim(),
+ },
+ ];
+
+ return {
+ envs,
+ domains,
+ mounts,
+ };
+}
diff --git a/apps/dokploy/templates/teable/docker-compose.yml b/apps/dokploy/templates/teable/docker-compose.yml
index b96b677c0..386e37738 100644
--- a/apps/dokploy/templates/teable/docker-compose.yml
+++ b/apps/dokploy/templates/teable/docker-compose.yml
@@ -2,7 +2,7 @@ version: "3.9"
services:
teable:
- image: ghcr.io/teableio/teable:1.3.1-alpha-build.460
+ image: ghcr.io/teableio/teable:latest
restart: always
volumes:
- teable-data:/app/.assets
@@ -41,8 +41,7 @@ services:
- POSTGRES_DB=${POSTGRES_DB}
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- networks:
- - dokploy-network
+
healthcheck:
test:
[
@@ -58,8 +57,7 @@ services:
environment:
- TZ=${TIMEZONE}
- PRISMA_DATABASE_URL=postgresql://${POSTGRES_USER}:${POSTGRES_PASSWORD}@${POSTGRES_HOST}:${POSTGRES_PORT}/${POSTGRES_DB}
- networks:
- - dokploy-network
+
depends_on:
teable-db:
condition: service_healthy
diff --git a/apps/dokploy/templates/templates.ts b/apps/dokploy/templates/templates.ts
index 9531eb7ae..d39465a8e 100644
--- a/apps/dokploy/templates/templates.ts
+++ b/apps/dokploy/templates/templates.ts
@@ -1,6 +1,37 @@
import type { TemplateData } from "./types/templates-data.type";
export const templates: TemplateData[] = [
+ {
+ id: "appwrite",
+ name: "Appwrite",
+ version: "1.6.0",
+ description:
+ "Appwrite is an end-to-end backend server for Web, Mobile, Native, or Backend apps. Appwrite abstracts the complexity and repetitiveness required to build a modern backend API from scratch and allows you to build secure apps faster.\n" +
+ "Using Appwrite, you can easily integrate your app with user authentication and multiple sign-in methods, a database for storing and querying users and team data, storage and file management, image manipulation, Cloud Functions, messaging, and more services.",
+ links: {
+ github: "https://github.com/appwrite/appwrite",
+ website: "https://appwrite.io/",
+ docs: "https://appwrite.io/docs",
+ },
+ logo: "appwrite.svg",
+ tags: ["database", "firebase", "postgres"],
+ load: () => import("./appwrite/index").then((m) => m.generate),
+ },
+ {
+ id: "outline",
+ name: "Outline",
+ version: "0.82.0",
+ description:
+ "Outline is a self-hosted knowledge base and documentation platform that allows you to build and manage your own knowledge base applications.",
+ links: {
+ github: "https://github.com/outline/outline",
+ website: "https://getoutline.com/",
+ docs: "https://docs.getoutline.com/s/guide",
+ },
+ logo: "outline.png",
+ load: () => import("./outline/index").then((m) => m.generate),
+ tags: ["documentation", "knowledge-base", "self-hosted"],
+ },
{
id: "supabase",
name: "SupaBase",
@@ -34,7 +65,7 @@ export const templates: TemplateData[] = [
{
id: "plausible",
name: "Plausible",
- version: "v2.1.4",
+ version: "v2.1.5",
description:
"Plausible is a open source, self-hosted web analytics platform that lets you track website traffic and user behavior.",
logo: "plausible.svg",
@@ -170,7 +201,7 @@ export const templates: TemplateData[] = [
{
id: "wordpress",
name: "Wordpress",
- version: "5.8.3",
+ version: "6.7.1",
description:
"Wordpress is a free and open source content management system (CMS) for publishing and managing websites.",
logo: "wordpress.png",
@@ -362,6 +393,21 @@ export const templates: TemplateData[] = [
tags: ["chat"],
load: () => import("./open-webui/index").then((m) => m.generate),
},
+ {
+ id: "mailpit",
+ name: "Mailpit",
+ version: "v1.22.3",
+ description:
+ "Mailpit is a tiny, self-contained, and secure email & SMTP testing tool with API for developers.",
+ logo: "mailpit.svg",
+ links: {
+ github: "https://github.com/axllent/mailpit",
+ website: "https://mailpit.axllent.org/",
+ docs: "https://mailpit.axllent.org/docs/",
+ },
+ tags: ["email", "smtp"],
+ load: () => import("./mailpit/index").then((m) => m.generate),
+ },
{
id: "listmonk",
name: "Listmonk",
@@ -395,7 +441,7 @@ export const templates: TemplateData[] = [
{
id: "umami",
name: "Umami",
- version: "v2.14.0",
+ version: "v2.16.1",
description:
"Umami is a simple, fast, privacy-focused alternative to Google Analytics.",
logo: "umami.png",
@@ -538,7 +584,7 @@ export const templates: TemplateData[] = [
website: "https://filebrowser.org/",
docs: "https://filebrowser.org/",
},
- tags: ["file", "manager"],
+ tags: ["file-manager", "storage"],
load: () => import("./filebrowser/index").then((m) => m.generate),
},
{
@@ -631,6 +677,21 @@ export const templates: TemplateData[] = [
tags: ["open-source"],
load: () => import("./vaultwarden/index").then((m) => m.generate),
},
+ {
+ id: "linkwarden",
+ name: "Linkwarden",
+ version: "2.9.3",
+ description:
+ "Self-hosted, open-source collaborative bookmark manager to collect, organize and archive webpages.",
+ logo: "linkwarden.png",
+ links: {
+ github: "https://github.com/linkwarden/linkwarden",
+ website: "https://linkwarden.app/",
+ docs: "https://docs.linkwarden.app/",
+ },
+ tags: ["bookmarks", "link-sharing"],
+ load: () => import("./linkwarden/index").then((m) => m.generate),
+ },
{
id: "hi-events",
name: "Hi.events",
@@ -834,7 +895,7 @@ export const templates: TemplateData[] = [
website: "https://nextcloud.com/",
docs: "https://docs.nextcloud.com/",
},
- tags: ["file", "sync"],
+ tags: ["file-manager", "sync"],
load: () => import("./nextcloud-aio/index").then((m) => m.generate),
},
{
@@ -1062,6 +1123,21 @@ export const templates: TemplateData[] = [
tags: ["identity", "auth"],
load: () => import("./logto/index").then((m) => m.generate),
},
+ {
+ id: "pocket-id",
+ name: "Pocket ID",
+ version: "0.35.1",
+ description:
+ "A simple and easy-to-use OIDC provider that allows users to authenticate with their passkeys to your services.",
+ logo: "pocket-id.svg",
+ links: {
+ github: "https://github.com/pocket-id/pocket-id",
+ website: "https://pocket-id.org/",
+ docs: "https://pocket-id.org/docs",
+ },
+ tags: ["identity", "auth"],
+ load: () => import("./pocket-id/index").then((m) => m.generate),
+ },
{
id: "penpot",
name: "Penpot",
@@ -1074,7 +1150,7 @@ export const templates: TemplateData[] = [
website: "https://penpot.app/",
docs: "https://docs.penpot.app/",
},
- tags: ["desing", "collaboration"],
+ tags: ["design", "collaboration"],
load: () => import("./penpot/index").then((m) => m.generate),
},
{
@@ -1095,9 +1171,9 @@ export const templates: TemplateData[] = [
{
id: "unsend",
name: "Unsend",
- version: "v1.2.4",
+ version: "v1.3.2",
description: "Open source alternative to Resend,Sendgrid, Postmark etc. ",
- logo: "unsend.png", // we defined the name and the extension of the logo
+ logo: "unsend.png",
links: {
github: "https://github.com/unsend-dev/unsend",
website: "https://unsend.dev/",
@@ -1239,6 +1315,21 @@ export const templates: TemplateData[] = [
tags: ["matrix", "communication"],
load: () => import("./conduit/index").then((m) => m.generate),
},
+ {
+ id: "evolutionapi",
+ name: "Evolution API",
+ version: "v2.1.2",
+ description:
+ "Evolution API is a robust platform dedicated to empowering small businesses with limited resources, going beyond a simple messaging solution via WhatsApp.",
+ logo: "evolutionapi.png",
+ links: {
+ github: "https://github.com/EvolutionAPI/evolution-api",
+ docs: "https://doc.evolution-api.com/v2/en/get-started/introduction",
+ website: "https://evolution-api.com/opensource-whatsapp-api/",
+ },
+ tags: ["api", "whatsapp", "messaging"],
+ load: () => import("./evolutionapi/index").then((m) => m.generate),
+ },
{
id: "conduwuit",
name: "Conduwuit",
@@ -1276,11 +1367,11 @@ export const templates: TemplateData[] = [
version: "latest",
description:
"CouchDB is a document-oriented NoSQL database that excels at replication and horizontal scaling.",
- logo: "couchdb.png", // we defined the name and the extension of the logo
+ logo: "couchdb.png",
links: {
- github: "lorem",
- website: "lorem",
- docs: "lorem",
+ github: "https://github.com/apache/couchdb",
+ website: "https://couchdb.apache.org/",
+ docs: "https://docs.couchdb.org/en/stable/",
},
tags: ["database", "storage"],
load: () => import("./couchdb/index").then((m) => m.generate),
@@ -1298,4 +1389,233 @@ export const templates: TemplateData[] = [
tags: ["developer", "tools"],
load: () => import("./it-tools/index").then((m) => m.generate),
},
+ {
+ id: "superset",
+ name: "Superset (Unofficial)",
+ version: "latest",
+ description: "Data visualization and data exploration platform.",
+ logo: "superset.svg",
+ links: {
+ github: "https://github.com/amancevice/docker-superset",
+ website: "https://superset.apache.org",
+ docs: "https://superset.apache.org/docs/intro",
+ },
+ tags: ["analytics", "bi", "dashboard", "database", "sql"],
+ load: () => import("./superset/index").then((m) => m.generate),
+ },
+ {
+ id: "glance",
+ name: "Glance",
+ version: "latest",
+ description:
+ "A self-hosted dashboard that puts all your feeds in one place. Features RSS feeds, weather, bookmarks, site monitoring, and more in a minimal, fast interface.",
+ logo: "glance.png",
+ links: {
+ github: "https://github.com/glanceapp/glance",
+ docs: "https://github.com/glanceapp/glance/blob/main/docs/configuration.md",
+ },
+ tags: ["dashboard", "monitoring", "widgets", "rss"],
+ load: () => import("./glance/index").then((m) => m.generate),
+ },
+ {
+ id: "homarr",
+ name: "Homarr",
+ version: "latest",
+ description:
+ "A sleek, modern dashboard that puts all your apps and services in one place with Docker integration.",
+ logo: "homarr.png",
+ links: {
+ github: "https://github.com/homarr-labs/homarr",
+ docs: "https://homarr.dev/docs/getting-started/installation/docker",
+ website: "https://homarr.dev/",
+ },
+ tags: ["dashboard", "monitoring"],
+ load: () => import("./homarr/index").then((m) => m.generate),
+ },
+ {
+ id: "erpnext",
+ name: "ERPNext",
+ version: "version-15",
+ description: "100% Open Source and highly customizable ERP software.",
+ logo: "erpnext.svg",
+ links: {
+ github: "https://github.com/frappe/erpnext",
+ docs: "https://docs.frappe.io/erpnext",
+ website: "https://erpnext.com",
+ },
+ tags: [
+ "erp",
+ "accounts",
+ "manufacturing",
+ "retail",
+ "sales",
+ "pos",
+ "hrms",
+ ],
+ load: () => import("./erpnext/index").then((m) => m.generate),
+ },
+ {
+ id: "maybe",
+ name: "Maybe",
+ version: "latest",
+ description:
+ "Maybe is a self-hosted finance tracking application designed to simplify budgeting and expenses.",
+ logo: "maybe.svg",
+ links: {
+ github: "https://github.com/maybe-finance/maybe",
+ website: "https://maybe.finance/",
+ docs: "https://docs.maybe.finance/",
+ },
+ tags: ["finance", "self-hosted"],
+ load: () => import("./maybe/index").then((m) => m.generate),
+ },
+ {
+ id: "spacedrive",
+ name: "Spacedrive",
+ version: "latest",
+ description:
+ "Spacedrive is a cross-platform file manager. It connects your devices together to help you organize files from anywhere. powered by a virtual distributed filesystem (VDFS) written in Rust. Organize files across many devices in one place.",
+ links: {
+ github: "https://github.com/spacedriveapp/spacedrive",
+ website: "https://spacedrive.com/",
+ docs: "https://www.spacedrive.com/docs/product/getting-started/introduction",
+ },
+ logo: "spacedrive.png",
+ tags: ["file-manager", "vdfs", "storage"],
+ load: () => import("./spacedrive/index").then((m) => m.generate),
+ },
+ {
+ id: "registry",
+ name: "Docker Registry",
+ version: "2",
+ description:
+ "Distribution implementation for storing and distributing of Docker container images and artifacts.",
+ links: {
+ github: "https://github.com/distribution/distribution",
+ website: "https://hub.docker.com/_/registry",
+ docs: "https://distribution.github.io/distribution/",
+ },
+ logo: "registry.png",
+ tags: ["registry", "docker", "self-hosted"],
+ load: () => import("./registry/index").then((m) => m.generate),
+ },
+ {
+ id: "alist",
+ name: "AList",
+ version: "v3.41.0",
+ description:
+ "🗂️A file list/WebDAV program that supports multiple storages, powered by Gin and Solidjs.",
+ logo: "alist.svg",
+ links: {
+ github: "https://github.com/AlistGo/alist",
+ website: "https://alist.nn.ci",
+ docs: "https://alist.nn.ci/guide/install/docker.html",
+ },
+ tags: ["file", "webdav", "storage"],
+ load: () => import("./alist/index").then((m) => m.generate),
+ },
+ {
+ id: "answer",
+ name: "Answer",
+ version: "v1.4.1",
+ description:
+ "Answer is an open-source Q&A platform for building a self-hosted question-and-answer service.",
+ logo: "answer.png",
+ links: {
+ github: "https://github.com/apache/answer",
+ website: "https://answer.apache.org/",
+ docs: "https://answer.apache.org/docs",
+ },
+ tags: ["q&a", "self-hosted"],
+ load: () => import("./answer/index").then((m) => m.generate),
+ },
+ {
+ id: "shlink",
+ name: "Shlink",
+ version: "stable",
+ description:
+ "URL shortener that can be used to serve shortened URLs under your own domain.",
+ logo: "shlink.svg",
+ links: {
+ github: "https://github.com/shlinkio/shlink",
+ website: "https://shlink.io",
+ docs: "https://shlink.io/documentation",
+ },
+ tags: ["sharing", "shortener", "url"],
+ load: () => import("./shlink/index").then((m) => m.generate),
+ },
+ {
+ id: "frappe-hr",
+ name: "Frappe HR",
+ version: "version-15",
+ description:
+ "Feature rich HR & Payroll software. 100% FOSS and customizable.",
+ logo: "frappe-hr.svg",
+ links: {
+ github: "https://github.com/frappe/hrms",
+ docs: "https://docs.frappe.io/hr",
+ website: "https://frappe.io/hr",
+ },
+ tags: ["hrms", "payroll", "leaves", "expenses", "attendance", "performace"],
+ load: () => import("./frappe-hr/index").then((m) => m.generate),
+ },
+ {
+ id: "formbricks",
+ name: "Formbricks",
+ version: "v3.1.3",
+ description:
+ "Formbricks is an open-source survey and form platform for collecting user data.",
+ logo: "formbricks.png",
+ links: {
+ github: "https://github.com/formbricks/formbricks",
+ website: "https://formbricks.com/",
+ docs: "https://formbricks.com/docs",
+ },
+ tags: ["forms", "analytics"],
+ load: () => import("./formbricks/index").then((m) => m.generate),
+ },
+ {
+ id: "trilium",
+ name: "Trilium",
+ description:
+ "Trilium Notes is a hierarchical note taking application with focus on building large personal knowledge bases.",
+ logo: "trilium.png",
+ version: "latest",
+ links: {
+ github: "https://github.com/zadam/trilium",
+ website: "https://github.com/zadam/trilium",
+ docs: "https://github.com/zadam/trilium/wiki/",
+ },
+ tags: ["self-hosted", "productivity", "personal-use"],
+ load: () => import("./trilium/index").then((m) => m.generate),
+ },
+ {
+ id: "convex",
+ name: "Convex",
+ version: "latest",
+ description:
+ "Convex is an open-source reactive database designed to make life easy for web app developers.",
+ logo: "convex.svg",
+ links: {
+ github: "https://github.com/get-convex/convex",
+ website: "https://www.convex.dev/",
+ docs: "https://www.convex.dev/docs",
+ },
+ tags: ["backend", "database", "api"],
+ load: () => import("./convex/index").then((m) => m.generate),
+ },
+ {
+ id: "wikijs",
+ name: "Wiki.js",
+ version: "2.5",
+ description: "The most powerful and extensible open source Wiki software.",
+ logo: "wikijs.svg",
+ links: {
+ github: "https://github.com/requarks/wiki",
+ website: "https://js.wiki/",
+ docs: "https://docs.requarks.io/",
+ },
+ tags: ["knowledge-base", "self-hosted", "documentation"],
+ load: () => import("./wikijs/index").then((m) => m.generate),
+ },
];
diff --git a/apps/dokploy/templates/triggerdotdev/index.ts b/apps/dokploy/templates/triggerdotdev/index.ts
index 7b894acba..c11c708b5 100644
--- a/apps/dokploy/templates/triggerdotdev/index.ts
+++ b/apps/dokploy/templates/triggerdotdev/index.ts
@@ -1,4 +1,3 @@
-import { Secrets } from "@/components/ui/secrets";
import {
type DomainSchema,
type Schema,
diff --git a/apps/dokploy/templates/trilium/docker-compose.yml b/apps/dokploy/templates/trilium/docker-compose.yml
new file mode 100644
index 000000000..f549d8204
--- /dev/null
+++ b/apps/dokploy/templates/trilium/docker-compose.yml
@@ -0,0 +1,14 @@
+services:
+ trilium:
+ image: zadam/trilium:latest
+ ports:
+ - 8080
+ networks:
+ - dokploy-network
+ restart: always
+ volumes:
+ - /root/trilium-backups:/home/node/trilium-data/backup
+
+networks:
+ dokploy-network:
+ external: true
diff --git a/apps/dokploy/templates/trilium/index.ts b/apps/dokploy/templates/trilium/index.ts
new file mode 100644
index 000000000..acac98413
--- /dev/null
+++ b/apps/dokploy/templates/trilium/index.ts
@@ -0,0 +1,22 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const triliumDomain = generateRandomDomain(schema);
+
+ const domains: DomainSchema[] = [
+ {
+ host: triliumDomain,
+ port: 8080,
+ serviceName: "trilium",
+ },
+ ];
+
+ return {
+ domains,
+ };
+}
diff --git a/apps/dokploy/templates/twenty/docker-compose.yml b/apps/dokploy/templates/twenty/docker-compose.yml
index 34c70aeb3..800e9a724 100644
--- a/apps/dokploy/templates/twenty/docker-compose.yml
+++ b/apps/dokploy/templates/twenty/docker-compose.yml
@@ -4,8 +4,7 @@ services:
twenty-change-vol-ownership:
image: ubuntu
user: root
- networks:
- - dokploy-network
+
volumes:
- twenty-server-local-data:/tmp/server-local-data
- twenty-docker-data:/tmp/docker-data
@@ -16,8 +15,7 @@ services:
twenty-server:
image: twentycrm/twenty:latest
- networks:
- - dokploy-network
+
volumes:
- twenty-server-local-data:/app/packages/twenty-server/${STORAGE_LOCAL_PATH:-.local-storage}
- twenty-docker-data:/app/docker-data
@@ -45,8 +43,7 @@ services:
twenty-worker:
image: twentycrm/twenty:latest
- networks:
- - dokploy-network
+
command: ["yarn", "worker:prod"]
environment:
PG_DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@twenty-postgres:5432/twenty
@@ -65,8 +62,7 @@ services:
twenty-postgres:
image: postgres:16-alpine
- networks:
- - dokploy-network
+
volumes:
- twenty-postgres-data:/var/lib/postgresql/data
environment:
@@ -82,8 +78,7 @@ services:
twenty-redis:
image: redis:latest
- networks:
- - dokploy-network
+
volumes:
- twenty-redis-data:/data
healthcheck:
diff --git a/apps/dokploy/templates/typebot/docker-compose.yml b/apps/dokploy/templates/typebot/docker-compose.yml
index 739793fe2..7881bd8f6 100644
--- a/apps/dokploy/templates/typebot/docker-compose.yml
+++ b/apps/dokploy/templates/typebot/docker-compose.yml
@@ -13,8 +13,7 @@ services:
POSTGRES_USER: typebot
POSTGRES_DB: typebot
POSTGRES_PASSWORD: typebot
- networks:
- - dokploy-network
+
typebot-builder:
image: baptistearno/typebot-builder:2.27
diff --git a/apps/dokploy/templates/umami/docker-compose.yml b/apps/dokploy/templates/umami/docker-compose.yml
index 191c4803d..26efd337c 100644
--- a/apps/dokploy/templates/umami/docker-compose.yml
+++ b/apps/dokploy/templates/umami/docker-compose.yml
@@ -1,6 +1,6 @@
services:
umami:
- image: ghcr.io/umami-software/umami:postgresql-v2.14.0
+ image: ghcr.io/umami-software/umami:postgresql-v2.16.1
restart: always
healthcheck:
test: ["CMD-SHELL", "curl http://localhost:3000/api/heartbeat"]
@@ -22,8 +22,7 @@ services:
interval: 5s
timeout: 5s
retries: 5
- networks:
- - dokploy-network
+
volumes:
- db-data:/var/lib/postgresql/data
environment:
diff --git a/apps/dokploy/templates/unifi/docker-compose.yml b/apps/dokploy/templates/unifi/docker-compose.yml
index ee531f67a..cf0102c00 100644
--- a/apps/dokploy/templates/unifi/docker-compose.yml
+++ b/apps/dokploy/templates/unifi/docker-compose.yml
@@ -29,8 +29,7 @@ services:
restart: unless-stopped
depends_on:
- unifi-db
- networks:
- - dokploy-network
+
unifi-db:
image: mongo:4.4
@@ -40,8 +39,7 @@ services:
ports:
- 27017
restart: unless-stopped
- networks:
- - dokploy-network
+
networks:
dokploy-network:
diff --git a/apps/dokploy/templates/unifi/index.ts b/apps/dokploy/templates/unifi/index.ts
index 975ce63d9..ea67b0fae 100644
--- a/apps/dokploy/templates/unifi/index.ts
+++ b/apps/dokploy/templates/unifi/index.ts
@@ -1,6 +1,6 @@
import type { Schema, Template } from "../utils";
-export function generate(schema: Schema): Template {
+export function generate(_schema: Schema): Template {
const mounts: Template["mounts"] = [
{
filePath: "init-mongo.sh",
diff --git a/apps/dokploy/templates/unsend/docker-compose.yml b/apps/dokploy/templates/unsend/docker-compose.yml
index cdf02de6c..93e802951 100644
--- a/apps/dokploy/templates/unsend/docker-compose.yml
+++ b/apps/dokploy/templates/unsend/docker-compose.yml
@@ -3,8 +3,7 @@ name: unsend-prod
services:
unsend-db-prod:
image: postgres:16
- networks:
- - dokploy-network
+
restart: always
environment:
- POSTGRES_USER=${POSTGRES_USER:?err}
@@ -22,8 +21,7 @@ services:
unsend-redis-prod:
image: redis:7
- networks:
- - dokploy-network
+
restart: always
# ports:
# - "6379:6379"
@@ -33,8 +31,7 @@ services:
unsend-storage-prod:
image: minio/minio:RELEASE.2024-11-07T00-52-20Z
- networks:
- - dokploy-network
+
ports:
- 9002
- 9001
@@ -47,9 +44,7 @@ services:
command: -c 'mkdir -p /data/unsend && minio server /data --console-address ":9001" --address ":9002"'
unsend:
- image: unsend/unsend:v1.2.4
- networks:
- - dokploy-network
+ image: unsend/unsend:v1.3.2
restart: always
ports:
- ${PORT:-3000}
diff --git a/apps/dokploy/templates/unsend/index.ts b/apps/dokploy/templates/unsend/index.ts
index 1c4c9c715..dcc80f66e 100644
--- a/apps/dokploy/templates/unsend/index.ts
+++ b/apps/dokploy/templates/unsend/index.ts
@@ -3,7 +3,6 @@ import {
type Schema,
type Template,
generateBase64,
- generateHash,
generateRandomDomain,
} from "../utils";
diff --git a/apps/dokploy/templates/utils/index.ts b/apps/dokploy/templates/utils/index.ts
index b5369b916..941afc806 100644
--- a/apps/dokploy/templates/utils/index.ts
+++ b/apps/dokploy/templates/utils/index.ts
@@ -12,7 +12,9 @@ export interface Schema {
projectName: string;
}
-export type DomainSchema = Pick;
+export type DomainSchema = Pick & {
+ path?: string;
+};
export interface Template {
envs?: string[];
diff --git a/apps/dokploy/templates/wikijs/docker-compose.yml b/apps/dokploy/templates/wikijs/docker-compose.yml
new file mode 100644
index 000000000..6b21423d1
--- /dev/null
+++ b/apps/dokploy/templates/wikijs/docker-compose.yml
@@ -0,0 +1,31 @@
+version: '3.5'
+services:
+ wiki:
+ image: ghcr.io/requarks/wiki:2.5
+ restart: unless-stopped
+ environment:
+ - DB_TYPE
+ - DB_HOST
+ - DB_PORT
+ - DB_USER
+ - DB_PASS
+ - DB_NAME
+ depends_on:
+ - db
+ labels:
+ - traefik.enable=true
+ - traefik.constraint-label-stack=wikijs
+ db:
+ image: postgres:14
+ restart: unless-stopped
+ environment:
+ - POSTGRES_USER
+ - POSTGRES_PASSWORD
+ - POSTGRES_DB
+ volumes:
+ - wiki-db-data:/var/lib/postgresql/data
+networks:
+ dokploy-network:
+ external: true
+volumes:
+ wiki-db-data:
diff --git a/apps/dokploy/templates/wikijs/index.ts b/apps/dokploy/templates/wikijs/index.ts
new file mode 100644
index 000000000..ff6c234de
--- /dev/null
+++ b/apps/dokploy/templates/wikijs/index.ts
@@ -0,0 +1,35 @@
+import {
+ type DomainSchema,
+ type Schema,
+ type Template,
+ generateRandomDomain,
+} from "../utils";
+
+export function generate(schema: Schema): Template {
+ const domains: DomainSchema[] = [
+ {
+ host: generateRandomDomain(schema),
+ port: 3000,
+ serviceName: "wiki",
+ },
+ ];
+
+ const envs = [
+ "# Database Setup",
+ "POSTGRES_USER=wikijs",
+ "POSTGRES_PASSWORD=wikijsrocks",
+ "POSTGRES_DB=wiki",
+ "# WikiJS Database Connection",
+ "DB_TYPE=postgres",
+ "DB_HOST=db",
+ "DB_PORT=5432",
+ "DB_USER=wikijs",
+ "DB_PASS=wikijsrocks",
+ "DB_NAME=wiki",
+ ];
+
+ return {
+ domains,
+ envs,
+ };
+}
diff --git a/apps/dokploy/templates/windmill/docker-compose.yml b/apps/dokploy/templates/windmill/docker-compose.yml
index de91ee55f..9e91fa0ab 100644
--- a/apps/dokploy/templates/windmill/docker-compose.yml
+++ b/apps/dokploy/templates/windmill/docker-compose.yml
@@ -7,8 +7,7 @@ services:
restart: unless-stopped
volumes:
- windmill-postgres-data:/var/lib/postgresql/data
- networks:
- - dokploy-network
+
environment:
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: windmill
@@ -20,8 +19,7 @@ services:
windmill-server:
image: ghcr.io/windmill-labs/windmill:main
- networks:
- - dokploy-network
+
restart: unless-stopped
environment:
- DATABASE_URL=${DATABASE_URL}
@@ -42,8 +40,7 @@ services:
cpus: "1"
memory: 2048M
restart: unless-stopped
- networks:
- - dokploy-network
+
environment:
- DATABASE_URL=${DATABASE_URL}
- MODE=worker
@@ -65,8 +62,7 @@ services:
cpus: "0.1"
memory: 128M
restart: unless-stopped
- networks:
- - dokploy-network
+
environment:
- DATABASE_URL=${DATABASE_URL}
- MODE=worker
@@ -82,16 +78,14 @@ services:
windmill-lsp:
image: ghcr.io/windmill-labs/windmill-lsp:latest
restart: unless-stopped
- networks:
- - dokploy-network
+
volumes:
- windmill-lsp-cache:/root/.cache
windmill-caddy:
image: ghcr.io/windmill-labs/caddy-l4:latest
restart: unless-stopped
- networks:
- - dokploy-network
+
volumes:
- ../files/Caddyfile:/etc/caddy/Caddyfile
environment:
diff --git a/apps/dokploy/templates/wordpress/docker-compose.yml b/apps/dokploy/templates/wordpress/docker-compose.yml
index 7647859d8..f2fc4d9a5 100644
--- a/apps/dokploy/templates/wordpress/docker-compose.yml
+++ b/apps/dokploy/templates/wordpress/docker-compose.yml
@@ -12,8 +12,6 @@ services:
db:
image: mysql:5.7.34
- networks:
- - dokploy-network
environment:
MYSQL_DATABASE: exampledb
MYSQL_USER: exampleuser
diff --git a/apps/dokploy/templates/yourls/docker-compose.yml b/apps/dokploy/templates/yourls/docker-compose.yml
index ff2e14d93..f4aa16e35 100644
--- a/apps/dokploy/templates/yourls/docker-compose.yml
+++ b/apps/dokploy/templates/yourls/docker-compose.yml
@@ -3,8 +3,7 @@ version: '3.7'
services:
yourls-app:
image: yourls:1.9.2
- networks:
- - dokploy-network
+
environment:
YOURLS_SITE: https://${YOURLS_HOST}
YOURLS_USER: ${YOURLS_ADMIN_USER}
@@ -22,8 +21,7 @@ services:
yourls-mysql:
image: mysql:5.7
- networks:
- - dokploy-network
+
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: yourls
diff --git a/apps/dokploy/templates/zipline/docker-compose.yml b/apps/dokploy/templates/zipline/docker-compose.yml
index 808b0b89a..e29132df0 100644
--- a/apps/dokploy/templates/zipline/docker-compose.yml
+++ b/apps/dokploy/templates/zipline/docker-compose.yml
@@ -2,8 +2,7 @@ version: "3"
services:
postgres:
image: postgres:15
- networks:
- - dokploy-network
+
restart: unless-stopped
environment:
- POSTGRES_USER=postgres
diff --git a/apps/monitoring/.gitignore b/apps/monitoring/.gitignore
new file mode 100644
index 000000000..b3532fce9
--- /dev/null
+++ b/apps/monitoring/.gitignore
@@ -0,0 +1 @@
+monitoring.db
\ No newline at end of file
diff --git a/apps/monitoring/LICENSE.md b/apps/monitoring/LICENSE.md
new file mode 100644
index 000000000..e69de29bb
diff --git a/apps/monitoring/README.md b/apps/monitoring/README.md
new file mode 100644
index 000000000..3d0e1fa61
--- /dev/null
+++ b/apps/monitoring/README.md
@@ -0,0 +1,154 @@
+# Dokploy Monitoring (Go Version)
+
+Application that powers Dokploy's monitoring service.
+
+You can use it for monitoring any external service.
+
+## Requirements
+
+- Go 1.21
+
+## Configuration
+
+Create a `.env` file in the root of the project with the following content:
+
+```shell
+METRICS_CONFIG='{
+ "server": {
+ "refreshRate": 25,
+ "port": 3001,
+ "type": "Remote | Dokploy",
+ "token": "metrics",
+ "urlCallback": "http://localhost:3000/api/trpc/notification.receiveNotification",
+ "retentionDays": 7,
+ "cronJob": "0 0 * * *",
+ "thresholds": {
+ "cpu": 0,
+ "memory": 0
+ }
+ },
+ "containers": {
+ "refreshRate": 25,
+ "services": {
+ "include": ["testing-elasticsearch-14649e"],
+ "exclude": []
+ }
+ }
+}'
+```
+
+## Installation
+
+```bash
+go mod download
+```
+
+## Execution
+
+```bash
+go run main.go
+```
+
+## Endpoints
+
+- `GET /health` - Check service health status (no authentication required)
+- `GET /metrics?limit=` - Get server metrics (default limit: 50)
+- `GET /metrics/containers?limit=&appName=` - Get container metrics for a specific application (default limit: 50)
+
+## Features
+
+### Server
+
+- CPU Usage (%)
+- Memory Usage (%)
+- Disk
+- Network
+- CPU Model
+- Operating System
+- Kernel
+- Architecture
+- Threads
+
+Example response:
+
+| Field | Value |
+| ------------------ | --------------------------- |
+| timestamp | 2025-01-19T21:44:54.232164Z |
+| cpu | 24.57% |
+| cpu_model | Apple M1 Pro |
+| cpu_cores | 8 |
+| cpu_physical_cores | 1 |
+| cpu_speed | 3228.0 MHz |
+| os | darwin |
+| distro | darwin |
+| kernel | 23.4.0 |
+| arch | arm64 |
+| mem_used | 81.91% |
+| mem_used_gb | 13.11 GB |
+| mem_total | 16.0 GB |
+| uptime | 752232s |
+| disk_used | 89.34% |
+| total_disk | 460.43 GB |
+| network_in | 54.78 MB |
+| network_out | 31.72 MB |
+
+### Containers
+
+Compatible with all Docker container types (standalone containers, Docker Compose, and Docker Swarm stacks). Note: When monitoring Docker Compose or Swarm stacks, use the `--p` flag to properly identify all services within the stack.
+
+Example response:
+
+| Field | Value |
+| -------------- | ------------------------------------- |
+| id | 1 |
+| timestamp | 2025-01-19T22:16:30.796129Z |
+| container_id | 7428f5a49039 |
+| container_name | testing-elasticsearch-14649e-kibana-1 |
+
+Metrics JSON:
+
+```json
+{
+ "timestamp": "2025-01-19T22:16:30.796129Z",
+ "CPU": 83.76,
+ "Memory": {
+ "percentage": 0.03,
+ "used": 2.262,
+ "total": 7.654,
+ "usedUnit": "MB",
+ "totalUnit": "GB"
+ },
+ "Network": {
+ "input": 306,
+ "output": 0,
+ "inputUnit": "B",
+ "outputUnit": "B"
+ },
+ "BlockIO": {
+ "read": 28.7,
+ "write": 0,
+ "readUnit": "kB",
+ "writeUnit": "B"
+ },
+ "Container": "7428f5a49039",
+ "ID": "7428f5a49039",
+ "Name": "testing-elasticsearch-14649e-kibana-1"
+}
+```
+
+## Notifications
+
+Dokploy uses a callback URL to send notifications when metrics exceed configured thresholds. Notifications are sent via POST request in the following format:
+
+Note: Setting a threshold to 0 disables notifications for that metric.
+
+```typescript
+interface Notification {
+ Type: "Memory" | "CPU";
+ Value: number;
+ Threshold: number;
+ Message: string;
+ Timestamp: string;
+ Token: string;
+}
+```
diff --git a/apps/monitoring/config/metrics.go b/apps/monitoring/config/metrics.go
new file mode 100644
index 000000000..568b2d065
--- /dev/null
+++ b/apps/monitoring/config/metrics.go
@@ -0,0 +1,57 @@
+package config
+
+import (
+ "encoding/json"
+ "log"
+ "os"
+ "sync"
+)
+
+type Config struct {
+ Server struct {
+ ServerType string `json:"type"`
+ RefreshRate int `json:"refreshRate"`
+ Port int `json:"port"`
+ Token string `json:"token"`
+ UrlCallback string `json:"urlCallback"`
+ CronJob string `json:"cronJob"`
+ RetentionDays int `json:"retentionDays"`
+ Thresholds struct {
+ CPU int `json:"cpu"`
+ Memory int `json:"memory"`
+ } `json:"thresholds"`
+ } `json:"server"`
+ Containers struct {
+ RefreshRate int `json:"refreshRate"`
+ Services struct {
+ Include []string `json:"include"`
+ Exclude []string `json:"exclude"`
+ } `json:"services"`
+ } `json:"containers"`
+}
+
+var (
+ config *Config
+ configOnce sync.Once
+)
+
+func GetMetricsConfig() *Config {
+ configOnce.Do(func() {
+ configJSON := os.Getenv("METRICS_CONFIG")
+ if configJSON == "" {
+ log.Fatal("METRICS_CONFIG environment variable is required")
+ }
+
+ config = &Config{}
+ if err := json.Unmarshal([]byte(configJSON), config); err != nil {
+ log.Fatalf("Error parsing METRICS_CONFIG: %v", err)
+ }
+
+ // Validate required fields
+ if config.Server.Token == "" || config.Server.UrlCallback == "" {
+ log.Fatal("token and urlCallback are required in the configuration")
+ }
+ })
+
+ return config
+}
diff --git a/apps/monitoring/containers/config.go b/apps/monitoring/containers/config.go
new file mode 100644
index 000000000..a974bbe3f
--- /dev/null
+++ b/apps/monitoring/containers/config.go
@@ -0,0 +1,61 @@
+package containers
+
+import (
+ "strings"
+
+ "github.com/mauriciogm/dokploy/apps/monitoring/config"
+)
+
+var monitorConfig *MonitoringConfig
+
+func LoadConfig() error {
+ cfg := config.GetMetricsConfig()
+ monitorConfig = &MonitoringConfig{
+ IncludeServices: make([]string, len(cfg.Containers.Services.Include)),
+ ExcludeServices: make([]string, len(cfg.Containers.Services.Exclude)),
+ }
+
+ // Convert Include services
+ for i, svc := range cfg.Containers.Services.Include {
+ monitorConfig.IncludeServices[i] = svc
+ }
+
+ // Convert Exclude services
+ for i, appName := range cfg.Containers.Services.Exclude {
+ monitorConfig.ExcludeServices[i] = appName
+ }
+
+ return nil
+}
+
+func ShouldMonitorContainer(containerName string) bool {
+ if monitorConfig == nil {
+ return false
+ }
+
+ for _, excluded := range monitorConfig.ExcludeServices {
+ if strings.Contains(containerName, excluded) {
+ return false
+ }
+ }
+
+ if len(monitorConfig.IncludeServices) > 0 {
+ for _, included := range monitorConfig.IncludeServices {
+ if strings.Contains(containerName, included) {
+ return true
+ }
+ }
+ return false
+ }
+
+ return true
+}
+
+func GetServiceName(containerName string) string {
+ name := strings.TrimPrefix(containerName, "/")
+ parts := strings.Split(name, "-")
+ if len(parts) > 1 {
+ return strings.Join(parts[:len(parts)-1], "-")
+ }
+ return name
+}
diff --git a/apps/monitoring/containers/monitor.go b/apps/monitoring/containers/monitor.go
new file mode 100644
index 000000000..ff658642a
--- /dev/null
+++ b/apps/monitoring/containers/monitor.go
@@ -0,0 +1,270 @@
+package containers
+
+import (
+ "encoding/json"
+ "fmt"
+ "log"
+ "os/exec"
+ "strconv"
+ "strings"
+ "sync"
+ "time"
+
+ "github.com/mauriciogm/dokploy/apps/monitoring/config"
+ "github.com/mauriciogm/dokploy/apps/monitoring/database"
+)
+
+type ContainerMonitor struct {
+ db *database.DB
+ isRunning bool
+ mu sync.Mutex
+ stopChan chan struct{}
+}
+
+func NewContainerMonitor(db *database.DB) (*ContainerMonitor, error) {
+ if err := db.InitContainerMetricsTable(); err != nil {
+ return nil, fmt.Errorf("failed to initialize container metrics table: %v", err)
+ }
+
+ return &ContainerMonitor{
+ db: db,
+ stopChan: make(chan struct{}),
+ }, nil
+}
+
+func (cm *ContainerMonitor) Start() error {
+ if err := LoadConfig(); err != nil {
+ return fmt.Errorf("error loading config: %v", err)
+ }
+
+ // Check if there are services to monitor
+ if len(monitorConfig.IncludeServices) == 0 {
+ log.Printf("No services to monitor. Skipping container metrics collection")
+ return nil
+ }
+
+ metricsConfig := config.GetMetricsConfig()
+ refreshRate := metricsConfig.Containers.RefreshRate
+ if refreshRate == 0 {
+ refreshRate = 60 // default refresh rate
+ }
+ duration := time.Duration(refreshRate) * time.Second
+
+ // log.Printf("Container metrics collection will run every %d seconds for services: %v", refreshRate, monitorConfig.IncludeServices)
+
+ ticker := time.NewTicker(duration)
+ go func() {
+ for {
+ select {
+ case <-ticker.C:
+ // Check again in case the configuration has changed
+ if len(monitorConfig.IncludeServices) == 0 {
+ log.Printf("No services to monitor. Stopping metrics collection")
+ ticker.Stop()
+ return
+ }
+ cm.collectMetrics()
+ case <-cm.stopChan:
+ ticker.Stop()
+ return
+ }
+ }
+ }()
+
+ return nil
+}
+
+func (cm *ContainerMonitor) Stop() {
+ close(cm.stopChan)
+}
+
+func (cm *ContainerMonitor) collectMetrics() {
+ cm.mu.Lock()
+ if cm.isRunning {
+ cm.mu.Unlock()
+ log.Println("Previous collection still running, skipping...")
+ return
+ }
+ cm.isRunning = true
+ cm.mu.Unlock()
+
+ defer func() {
+ cm.mu.Lock()
+ cm.isRunning = false
+ cm.mu.Unlock()
+ }()
+
+ cmd := exec.Command("docker", "stats", "--no-stream", "--format",
+ `{"BlockIO":"{{.BlockIO}}","CPUPerc":"{{.CPUPerc}}","ID":"{{.ID}}","MemPerc":"{{.MemPerc}}","MemUsage":"{{.MemUsage}}","Name":"{{.Name}}","NetIO":"{{.NetIO}}"}`)
+
+ output, err := cmd.CombinedOutput()
+
+ // log.Printf("Output: %s", string(output))
+ if err != nil {
+ log.Printf("Error getting docker stats: %v", err)
+ return
+ }
+
+ lines := string(output)
+ if lines == "" {
+ return
+ }
+
+ seenServices := make(map[string]bool)
+ for _, line := range strings.Split(lines, "\n") {
+ if line == "" {
+ continue
+ }
+
+ var container Container
+ if err := json.Unmarshal([]byte(line), &container); err != nil {
+ log.Printf("Error parsing container data: %v", err)
+ continue
+ }
+
+ if !ShouldMonitorContainer(container.Name) {
+ continue
+ }
+
+ serviceName := GetServiceName(container.Name)
+
+ if seenServices[serviceName] {
+ continue
+ }
+
+ seenServices[serviceName] = true
+
+ // log.Printf("Container: %+v", container)
+
+ // Process metrics
+ metric := processContainerMetrics(container)
+
+ // log.Printf("Saving metrics for %s: %+v", serviceName, metric)
+
+ if err := cm.db.SaveContainerMetric(metric); err != nil {
+ log.Printf("Error saving metrics for %s: %v", serviceName, err)
+ }
+ }
+}
+
+func processContainerMetrics(container Container) *database.ContainerMetric {
+
+ // Process CPU
+ cpu, _ := strconv.ParseFloat(strings.TrimSuffix(container.CPUPerc, "%"), 64)
+
+ // Process Memory
+ memPerc, _ := strconv.ParseFloat(strings.TrimSuffix(container.MemPerc, "%"), 64)
+ memParts := strings.Split(container.MemUsage, " / ")
+
+ var usedValue, totalValue float64
+ var usedUnit, totalUnit string
+
+ if len(memParts) == 2 {
+ // Process used memory
+ usedParts := strings.Fields(memParts[0])
+ if len(usedParts) > 0 {
+ usedValue, _ = strconv.ParseFloat(strings.TrimRight(usedParts[0], "MiBGiB"), 64)
+ usedUnit = strings.TrimLeft(usedParts[0], "0123456789.")
+ // Convert MiB to MB and GiB to GB
+ if usedUnit == "MiB" {
+ usedUnit = "MB"
+ } else if usedUnit == "GiB" {
+ usedUnit = "GB"
+ }
+ }
+
+ // Process total memory
+ totalParts := strings.Fields(memParts[1])
+ if len(totalParts) > 0 {
+ totalValue, _ = strconv.ParseFloat(strings.TrimRight(totalParts[0], "MiBGiB"), 64)
+ totalUnit = strings.TrimLeft(totalParts[0], "0123456789.")
+ // Convert MiB to MB and GiB to GB
+ if totalUnit == "MiB" {
+ totalUnit = "MB"
+ } else if totalUnit == "GiB" {
+ totalUnit = "GB"
+ }
+ }
+ }
+
+ // Process Network I/O
+ netParts := strings.Split(container.NetIO, " / ")
+
+ var netInValue, netOutValue float64
+ var netInUnit, netOutUnit string
+
+ if len(netParts) == 2 {
+ // Process input
+ inParts := strings.Fields(netParts[0])
+ if len(inParts) > 0 {
+ netInValue, _ = strconv.ParseFloat(strings.TrimRight(inParts[0], "kMGTB"), 64)
+ netInUnit = strings.TrimLeft(inParts[0], "0123456789.")
+ }
+
+ // Process output
+ outParts := strings.Fields(netParts[1])
+ if len(outParts) > 0 {
+ netOutValue, _ = strconv.ParseFloat(strings.TrimRight(outParts[0], "kMGTB"), 64)
+ netOutUnit = strings.TrimLeft(outParts[0], "0123456789.")
+ }
+ }
+
+ // Process Block I/O
+ blockParts := strings.Split(container.BlockIO, " / ")
+
+ var blockReadValue, blockWriteValue float64
+ var blockReadUnit, blockWriteUnit string
+
+ if len(blockParts) == 2 {
+ // Process read
+ readParts := strings.Fields(blockParts[0])
+ if len(readParts) > 0 {
+ blockReadValue, _ = strconv.ParseFloat(strings.TrimRight(readParts[0], "kMGTB"), 64)
+ blockReadUnit = strings.TrimLeft(readParts[0], "0123456789.")
+ }
+
+ // Process write
+ writeParts := strings.Fields(blockParts[1])
+ if len(writeParts) > 0 {
+ blockWriteValue, _ = strconv.ParseFloat(strings.TrimRight(writeParts[0], "kMGTB"), 64)
+ blockWriteUnit = strings.TrimLeft(writeParts[0], "0123456789.")
+ }
+ }
+
+ return &database.ContainerMetric{
+ Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
+ CPU: cpu,
+ Memory: database.MemoryMetric{
+ Percentage: memPerc,
+ Used: usedValue,
+ Total: totalValue,
+ UsedUnit: usedUnit,
+ TotalUnit: totalUnit,
+ },
+ Network: database.NetworkMetric{
+ Input: netInValue,
+ Output: netOutValue,
+ InputUnit: netInUnit,
+ OutputUnit: netOutUnit,
+ },
+ BlockIO: database.BlockIOMetric{
+ Read: blockReadValue,
+ Write: blockWriteValue,
+ ReadUnit: blockReadUnit,
+ WriteUnit: blockWriteUnit,
+ },
+ Container: container.ID,
+ ID: container.ID,
+ Name: container.Name,
+ }
+}
+
+func parseValue(value string) (float64, string) {
+ parts := strings.Fields(value)
+ if len(parts) < 1 {
+ return 0, "B"
+ }
+ v, _ := strconv.ParseFloat(parts[0], 64)
+ unit := strings.TrimLeft(value, "0123456789.")
+ return v, unit
+}
diff --git a/apps/monitoring/containers/types.go b/apps/monitoring/containers/types.go
new file mode 100644
index 000000000..c1d1c110e
--- /dev/null
+++ b/apps/monitoring/containers/types.go
@@ -0,0 +1,48 @@
+package containers
+
+type Container struct {
+ BlockIO string `json:"BlockIO"`
+ CPUPerc string `json:"CPUPerc"`
+ ID string `json:"ID"`
+ MemPerc string `json:"MemPerc"`
+ MemUsage string `json:"MemUsage"`
+ Name string `json:"Name"`
+ NetIO string `json:"NetIO"`
+}
+
+type ContainerMetric struct {
+ Timestamp string `json:"timestamp"`
+ CPU float64 `json:"CPU"`
+ Memory MemoryMetric `json:"Memory"`
+ Network NetworkMetric `json:"Network"`
+ BlockIO BlockIOMetric `json:"BlockIO"`
+ Container string `json:"Container"`
+ ID string `json:"ID"`
+ Name string `json:"Name"`
+}
+
+type MemoryMetric struct {
+ Percentage float64 `json:"percentage"`
+ Used float64 `json:"used"`
+ Total float64 `json:"total"`
+ Unit string `json:"unit"`
+}
+
+type NetworkMetric struct {
+ Input float64 `json:"input"`
+ Output float64 `json:"output"`
+ InputUnit string `json:"inputUnit"`
+ OutputUnit string `json:"outputUnit"`
+}
+
+type BlockIOMetric struct {
+ Read float64 `json:"read"`
+ Write float64 `json:"write"`
+ ReadUnit string `json:"readUnit"`
+ WriteUnit string `json:"writeUnit"`
+}
+
+type MonitoringConfig struct {
+ IncludeServices []string `json:"includeServices"`
+ ExcludeServices []string `json:"excludeServices"`
+}
diff --git a/apps/monitoring/database/cleanup.go b/apps/monitoring/database/cleanup.go
new file mode 100644
index 000000000..9ab0bd26d
--- /dev/null
+++ b/apps/monitoring/database/cleanup.go
@@ -0,0 +1,52 @@
+package database
+
+import (
+ "database/sql"
+ "log"
+ "time"
+
+ "github.com/robfig/cron/v3"
+)
+
+// CleanupMetrics deletes metrics older than the retention period
+func CleanupMetrics(db *sql.DB, retentionDays int) error {
+ cutoffDate := time.Now().AddDate(0, 0, -retentionDays)
+ cutoffDateStr := cutoffDate.UTC().Format(time.RFC3339Nano)
+
+ containerQuery := `DELETE FROM container_metrics WHERE timestamp < ?`
+ _, err := db.Exec(containerQuery, cutoffDateStr)
+ if err != nil {
+ return err
+ }
+
+ serverQuery := `DELETE FROM server_metrics WHERE timestamp < ?`
+ _, err = db.Exec(serverQuery, cutoffDateStr)
+ if err != nil {
+ return err
+ }
+
+ log.Printf("Metrics deleted (older than %d days)", retentionDays)
+ log.Printf("Cutoff date for both tables: %s", cutoffDateStr)
+ return nil
+}
+
+// StartMetricsCleanup starts a cron job to periodically clean up metrics
+func StartMetricsCleanup(db *sql.DB, retentionDays int, cronExpression string) (*cron.Cron, error) {
+ c := cron.New()
+
+ _, err := c.AddFunc(cronExpression, func() {
+ if err := CleanupMetrics(db, retentionDays); err != nil {
+ log.Printf("Error during metrics cleanup: %v", err)
+ }
+ })
+
+ if err != nil {
+ return nil, err
+ }
+
+ c.Start()
+ log.Printf("Started metrics cleanup job (retention: %d days, cron: %s)",
+ retentionDays, cronExpression)
+
+ return c, nil
+}
diff --git a/apps/monitoring/database/containers.go b/apps/monitoring/database/containers.go
new file mode 100644
index 000000000..568ad12e5
--- /dev/null
+++ b/apps/monitoring/database/containers.go
@@ -0,0 +1,160 @@
+package database
+
+import (
+ "encoding/json"
+ "fmt"
+ "strings"
+)
+
+func (db *DB) InitContainerMetricsTable() error {
+ _, err := db.Exec(`
+ CREATE TABLE IF NOT EXISTS container_metrics (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ timestamp TEXT NOT NULL,
+ container_id TEXT NOT NULL,
+ container_name TEXT NOT NULL,
+ metrics_json TEXT NOT NULL
+ )
+ `)
+ if err != nil {
+ return fmt.Errorf("error creating container_metrics table: %v", err)
+ }
+
+ // Crear índices para mejorar el rendimiento
+ _, err = db.Exec(`CREATE INDEX IF NOT EXISTS idx_container_metrics_timestamp ON container_metrics(timestamp)`)
+ if err != nil {
+ return fmt.Errorf("error creating timestamp index: %v", err)
+ }
+
+ _, err = db.Exec(`CREATE INDEX IF NOT EXISTS idx_container_metrics_name ON container_metrics(container_name)`)
+ if err != nil {
+ return fmt.Errorf("error creating name index: %v", err)
+ }
+
+ return nil
+}
+
+func (db *DB) SaveContainerMetric(metric *ContainerMetric) error {
+ metricsJSON, err := json.Marshal(metric)
+ if err != nil {
+ return fmt.Errorf("error marshaling metrics: %v", err)
+ }
+
+ _, err = db.Exec(`
+ INSERT INTO container_metrics (timestamp, container_id, container_name, metrics_json)
+ VALUES (?, ?, ?, ?)
+ `, metric.Timestamp, metric.ID, metric.Name, string(metricsJSON))
+ return err
+}
+
+func (db *DB) GetLastNContainerMetrics(containerName string, limit int) ([]ContainerMetric, error) {
+ name := strings.TrimPrefix(containerName, "/")
+ parts := strings.Split(name, "-")
+ if len(parts) > 1 {
+ containerName = strings.Join(parts[:len(parts)-1], "-")
+ }
+
+ query := `
+ WITH recent_metrics AS (
+ SELECT metrics_json
+ FROM container_metrics
+ WHERE container_name LIKE ? || '%'
+ ORDER BY timestamp DESC
+ LIMIT ?
+ )
+ SELECT metrics_json FROM recent_metrics ORDER BY json_extract(metrics_json, '$.timestamp') ASC
+ `
+ rows, err := db.Query(query, containerName, limit)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var metrics []ContainerMetric
+ for rows.Next() {
+ var metricsJSON string
+ err := rows.Scan(&metricsJSON)
+ if err != nil {
+ return nil, err
+ }
+
+ var metric ContainerMetric
+ if err := json.Unmarshal([]byte(metricsJSON), &metric); err != nil {
+ return nil, err
+ }
+ metrics = append(metrics, metric)
+ }
+ return metrics, nil
+}
+
+func (db *DB) GetAllMetricsContainer(containerName string) ([]ContainerMetric, error) {
+ name := strings.TrimPrefix(containerName, "/")
+ parts := strings.Split(name, "-")
+ if len(parts) > 1 {
+ containerName = strings.Join(parts[:len(parts)-1], "-")
+ }
+
+ query := `
+ WITH recent_metrics AS (
+ SELECT metrics_json
+ FROM container_metrics
+ WHERE container_name LIKE ? || '%'
+ ORDER BY timestamp DESC
+ )
+ SELECT metrics_json FROM recent_metrics ORDER BY json_extract(metrics_json, '$.timestamp') ASC
+ `
+ rows, err := db.Query(query, containerName)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var metrics []ContainerMetric
+ for rows.Next() {
+ var metricsJSON string
+ err := rows.Scan(&metricsJSON)
+ if err != nil {
+ return nil, err
+ }
+
+ var metric ContainerMetric
+ if err := json.Unmarshal([]byte(metricsJSON), &metric); err != nil {
+ return nil, err
+ }
+ metrics = append(metrics, metric)
+ }
+ return metrics, nil
+}
+
+type ContainerMetric struct {
+ Timestamp string `json:"timestamp"`
+ CPU float64 `json:"CPU"`
+ Memory MemoryMetric `json:"Memory"`
+ Network NetworkMetric `json:"Network"`
+ BlockIO BlockIOMetric `json:"BlockIO"`
+ Container string `json:"Container"`
+ ID string `json:"ID"`
+ Name string `json:"Name"`
+}
+
+type MemoryMetric struct {
+ Percentage float64 `json:"percentage"`
+ Used float64 `json:"used"`
+ Total float64 `json:"total"`
+ UsedUnit string `json:"usedUnit"`
+ TotalUnit string `json:"totalUnit"`
+}
+
+type NetworkMetric struct {
+ Input float64 `json:"input"`
+ Output float64 `json:"output"`
+ InputUnit string `json:"inputUnit"`
+ OutputUnit string `json:"outputUnit"`
+}
+
+type BlockIOMetric struct {
+ Read float64 `json:"read"`
+ Write float64 `json:"write"`
+ ReadUnit string `json:"readUnit"`
+ WriteUnit string `json:"writeUnit"`
+}
diff --git a/apps/monitoring/database/db.go b/apps/monitoring/database/db.go
new file mode 100644
index 000000000..689564fc5
--- /dev/null
+++ b/apps/monitoring/database/db.go
@@ -0,0 +1,47 @@
+package database
+
+import (
+ "database/sql"
+
+ _ "github.com/mattn/go-sqlite3"
+)
+
+type DB struct {
+ *sql.DB
+}
+
+func InitDB() (*DB, error) {
+ db, err := sql.Open("sqlite3", "./monitoring.db")
+ if err != nil {
+ return nil, err
+ }
+
+ // Create metrics table if it doesn't exist
+ _, err = db.Exec(`
+ CREATE TABLE IF NOT EXISTS server_metrics (
+ timestamp TEXT PRIMARY KEY,
+ cpu REAL,
+ cpu_model TEXT,
+ cpu_cores INTEGER,
+ cpu_physical_cores INTEGER,
+ cpu_speed REAL,
+ os TEXT,
+ distro TEXT,
+ kernel TEXT,
+ arch TEXT,
+ mem_used REAL,
+ mem_used_gb REAL,
+ mem_total REAL,
+ uptime INTEGER,
+ disk_used REAL,
+ total_disk REAL,
+ network_in REAL,
+ network_out REAL
+ )
+ `)
+ if err != nil {
+ return nil, err
+ }
+
+ return &DB{db}, nil
+}
diff --git a/apps/monitoring/database/server.go b/apps/monitoring/database/server.go
new file mode 100644
index 000000000..64d22e3df
--- /dev/null
+++ b/apps/monitoring/database/server.go
@@ -0,0 +1,115 @@
+package database
+
+import (
+ "time"
+
+ _ "github.com/mattn/go-sqlite3"
+)
+
+type ServerMetric struct {
+ Timestamp string `json:"timestamp"`
+ CPU float64 `json:"cpu"`
+ CPUModel string `json:"cpuModel"`
+ CPUCores int32 `json:"cpuCores"`
+ CPUPhysicalCores int32 `json:"cpuPhysicalCores"`
+ CPUSpeed float64 `json:"cpuSpeed"`
+ OS string `json:"os"`
+ Distro string `json:"distro"`
+ Kernel string `json:"kernel"`
+ Arch string `json:"arch"`
+ MemUsed float64 `json:"memUsed"`
+ MemUsedGB float64 `json:"memUsedGB"`
+ MemTotal float64 `json:"memTotal"`
+ Uptime uint64 `json:"uptime"`
+ DiskUsed float64 `json:"diskUsed"`
+ TotalDisk float64 `json:"totalDisk"`
+ NetworkIn float64 `json:"networkIn"`
+ NetworkOut float64 `json:"networkOut"`
+}
+
+func (db *DB) SaveMetric(metric ServerMetric) error {
+ if metric.Timestamp == "" {
+ metric.Timestamp = time.Now().UTC().Format(time.RFC3339Nano)
+ }
+
+ _, err := db.Exec(`
+ INSERT INTO server_metrics (timestamp, cpu, cpu_model, cpu_cores, cpu_physical_cores, cpu_speed, os, distro, kernel, arch, mem_used, mem_used_gb, mem_total, uptime, disk_used, total_disk, network_in, network_out)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
+ `, metric.Timestamp, metric.CPU, metric.CPUModel, metric.CPUCores, metric.CPUPhysicalCores, metric.CPUSpeed, metric.OS, metric.Distro, metric.Kernel, metric.Arch, metric.MemUsed, metric.MemUsedGB, metric.MemTotal, metric.Uptime, metric.DiskUsed, metric.TotalDisk, metric.NetworkIn, metric.NetworkOut)
+ return err
+}
+
+func (db *DB) GetMetricsInRange(start, end time.Time) ([]ServerMetric, error) {
+ rows, err := db.Query(`
+ SELECT timestamp, cpu, cpu_model, cpu_cores, cpu_physical_cores, cpu_speed, os, distro, kernel, arch, mem_used, mem_used_gb, mem_total, uptime, disk_used, total_disk, network_in, network_out
+ FROM server_metrics
+ WHERE timestamp BETWEEN ? AND ?
+ ORDER BY timestamp ASC
+ `, start.UTC().Format(time.RFC3339Nano), end.UTC().Format(time.RFC3339Nano))
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var metrics []ServerMetric
+ for rows.Next() {
+ var m ServerMetric
+ err := rows.Scan(&m.Timestamp, &m.CPU, &m.CPUModel, &m.CPUCores, &m.CPUPhysicalCores, &m.CPUSpeed, &m.OS, &m.Distro, &m.Kernel, &m.Arch, &m.MemUsed, &m.MemUsedGB, &m.MemTotal, &m.Uptime, &m.DiskUsed, &m.TotalDisk, &m.NetworkIn, &m.NetworkOut)
+ if err != nil {
+ return nil, err
+ }
+ metrics = append(metrics, m)
+ }
+ return metrics, nil
+}
+
+func (db *DB) GetLastNMetrics(n int) ([]ServerMetric, error) {
+ rows, err := db.Query(`
+ WITH recent_metrics AS (
+ SELECT timestamp, cpu, cpu_model, cpu_cores, cpu_physical_cores, cpu_speed, os, distro, kernel, arch, mem_used, mem_used_gb, mem_total, uptime, disk_used, total_disk, network_in, network_out
+ FROM server_metrics
+ ORDER BY timestamp DESC
+ LIMIT ?
+ )
+ SELECT * FROM recent_metrics
+ ORDER BY timestamp ASC
+ `, n)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var metrics []ServerMetric
+ for rows.Next() {
+ var m ServerMetric
+ err := rows.Scan(&m.Timestamp, &m.CPU, &m.CPUModel, &m.CPUCores, &m.CPUPhysicalCores, &m.CPUSpeed, &m.OS, &m.Distro, &m.Kernel, &m.Arch, &m.MemUsed, &m.MemUsedGB, &m.MemTotal, &m.Uptime, &m.DiskUsed, &m.TotalDisk, &m.NetworkIn, &m.NetworkOut)
+ if err != nil {
+ return nil, err
+ }
+ metrics = append(metrics, m)
+ }
+ return metrics, nil
+}
+
+func (db *DB) GetAllMetrics() ([]ServerMetric, error) {
+ rows, err := db.Query(`
+ SELECT timestamp, cpu, cpu_model, cpu_cores, cpu_physical_cores, cpu_speed, os, distro, kernel, arch, mem_used, mem_used_gb, mem_total, uptime, disk_used, total_disk, network_in, network_out
+ FROM server_metrics
+ ORDER BY timestamp ASC
+ `)
+ if err != nil {
+ return nil, err
+ }
+ defer rows.Close()
+
+ var metrics []ServerMetric
+ for rows.Next() {
+ var m ServerMetric
+ err := rows.Scan(&m.Timestamp, &m.CPU, &m.CPUModel, &m.CPUCores, &m.CPUPhysicalCores, &m.CPUSpeed, &m.OS, &m.Distro, &m.Kernel, &m.Arch, &m.MemUsed, &m.MemUsedGB, &m.MemTotal, &m.Uptime, &m.DiskUsed, &m.TotalDisk, &m.NetworkIn, &m.NetworkOut)
+ if err != nil {
+ return nil, err
+ }
+ metrics = append(metrics, m)
+ }
+ return metrics, nil
+}
diff --git a/apps/monitoring/go.mod b/apps/monitoring/go.mod
new file mode 100644
index 000000000..2c1590b45
--- /dev/null
+++ b/apps/monitoring/go.mod
@@ -0,0 +1,34 @@
+module github.com/mauriciogm/dokploy/apps/monitoring
+
+go 1.20
+
+require (
+ github.com/gofiber/fiber/v2 v2.52.6
+ github.com/joho/godotenv v1.5.1
+ github.com/mattn/go-sqlite3 v1.14.24
+ github.com/shirou/gopsutil/v3 v3.24.5
+)
+
+require (
+ github.com/andybalholm/brotli v1.1.0 // indirect
+ github.com/go-ole/go-ole v1.2.6 // indirect
+ github.com/google/uuid v1.6.0 // indirect
+ github.com/klauspost/compress v1.17.9 // indirect
+ github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
+ github.com/mattn/go-colorable v0.1.13 // indirect
+ github.com/mattn/go-isatty v0.0.20 // indirect
+ github.com/mattn/go-runewidth v0.0.16 // indirect
+ github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c // indirect
+ github.com/rivo/uniseg v0.2.0 // indirect
+ github.com/robfig/cron/v3 v3.0.1 // indirect
+ github.com/shoenig/go-m1cpu v0.1.6 // indirect
+ github.com/tklauser/go-sysconf v0.3.14 // indirect
+ github.com/tklauser/numcpus v0.8.0 // indirect
+ github.com/valyala/bytebufferpool v1.0.0 // indirect
+ github.com/valyala/fasthttp v1.51.0 // indirect
+ github.com/valyala/tcplisten v1.0.0 // indirect
+ github.com/yusufpapurcu/wmi v1.2.4 // indirect
+ golang.org/x/sys v0.28.0 // indirect
+)
+
+replace github.com/mauriciogm/dokploy/apps/monitoring => ./
diff --git a/apps/monitoring/go.sum b/apps/monitoring/go.sum
new file mode 100644
index 000000000..7a4c9db82
--- /dev/null
+++ b/apps/monitoring/go.sum
@@ -0,0 +1,61 @@
+github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M=
+github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY=
+github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
+github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
+github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
+github.com/gofiber/fiber/v2 v2.52.6 h1:Rfp+ILPiYSvvVuIPvxrBns+HJp8qGLDnLJawAu27XVI=
+github.com/gofiber/fiber/v2 v2.52.6/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
+github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
+github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
+github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
+github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
+github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
+github.com/klauspost/compress v1.17.9 h1:6KIumPrER1LHsvBVuDa0r5xaG0Es51mhhB9BQB2qeMA=
+github.com/klauspost/compress v1.17.9/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 h1:6E+4a0GO5zZEnZ81pIr0yLvtUWk2if982qA3F3QD6H4=
+github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0/go.mod h1:zJYVVT2jmtg6P3p1VtQj7WsuWi/y4VnjVBn7F8KPB3I=
+github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
+github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
+github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
+github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
+github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
+github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
+github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
+github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
+github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c h1:ncq/mPwQF4JjgDlrVEn3C11VoGHZN7m8qihwgMEtzYw=
+github.com/power-devops/perfstat v0.0.0-20210106213030-5aafc221ea8c/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE=
+github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY=
+github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
+github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
+github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzGIFLtro=
+github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI=
+github.com/shirou/gopsutil/v3 v3.24.5/go.mod h1:bsoOS1aStSs9ErQ1WWfxllSeS1K5D+U30r2NfcubMVk=
+github.com/shoenig/go-m1cpu v0.1.6 h1:nxdKQNcEB6vzgA2E2bvzKIYRuNj7XNJ4S/aRSwKzFtM=
+github.com/shoenig/go-m1cpu v0.1.6/go.mod h1:1JJMcUBvfNwpq05QDQVAnx3gUHr9IYF7GNg9SUEw2VQ=
+github.com/shoenig/test v0.6.4 h1:kVTaSd7WLz5WZ2IaoM0RSzRsUD+m8wRR+5qvntpn4LU=
+github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
+github.com/tklauser/go-sysconf v0.3.14 h1:g5vzr9iPFFz24v2KZXs/pvpvh8/V9Fw6vQK5ZZb78yU=
+github.com/tklauser/go-sysconf v0.3.14/go.mod h1:1ym4lWMLUOhuBOPGtRcJm7tEGX4SCYNEEEtghGG/8uY=
+github.com/tklauser/numcpus v0.8.0 h1:Mx4Wwe/FjZLeQsK/6kt2EOepwwSl7SmJrK5bV/dXYgY=
+github.com/tklauser/numcpus v0.8.0/go.mod h1:ZJZlAY+dmR4eut8epnzf0u/VwodKmryxR8txiloSqBE=
+github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
+github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
+github.com/valyala/fasthttp v1.51.0 h1:8b30A5JlZ6C7AS81RsWjYMQmrZG6feChmgAolCl1SqA=
+github.com/valyala/fasthttp v1.51.0/go.mod h1:oI2XroL+lI7vdXyYoQk03bXBThfFl2cVdIA3Xl7cH8g=
+github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8=
+github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc=
+github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo0=
+github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0=
+github.com/zcalusic/sysinfo v1.1.3 h1:u/AVENkuoikKuIZ4sUEJ6iibpmQP6YpGD8SSMCrqAF0=
+github.com/zcalusic/sysinfo v1.1.3/go.mod h1:NX+qYnWGtJVPV0yWldff9uppNKU4h40hJIRPf/pGLv4=
+golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA=
+golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
diff --git a/apps/monitoring/main.go b/apps/monitoring/main.go
new file mode 100644
index 000000000..432ccd24e
--- /dev/null
+++ b/apps/monitoring/main.go
@@ -0,0 +1,164 @@
+package main
+
+import (
+ "log"
+ "os"
+ "strconv"
+ "time"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/gofiber/fiber/v2/middleware/cors"
+ "github.com/joho/godotenv"
+ "github.com/mauriciogm/dokploy/apps/monitoring/config"
+ "github.com/mauriciogm/dokploy/apps/monitoring/containers"
+ "github.com/mauriciogm/dokploy/apps/monitoring/database"
+ "github.com/mauriciogm/dokploy/apps/monitoring/middleware"
+ "github.com/mauriciogm/dokploy/apps/monitoring/monitoring"
+)
+
+func main() {
+ godotenv.Load()
+
+ // Get configuration
+ cfg := config.GetMetricsConfig()
+ token := cfg.Server.Token
+ METRICS_URL_CALLBACK := cfg.Server.UrlCallback
+ log.Printf("Environment variables:")
+ log.Printf("METRICS_CONFIG: %s", os.Getenv("METRICS_CONFIG"))
+
+ if token == "" || METRICS_URL_CALLBACK == "" {
+ log.Fatal("token and urlCallback are required in the configuration")
+ }
+
+ db, err := database.InitDB()
+ if err != nil {
+ log.Fatal(err)
+ }
+
+ // Iniciar el sistema de limpieza de métricas
+ cleanupCron, err := database.StartMetricsCleanup(db.DB, cfg.Server.RetentionDays, cfg.Server.CronJob)
+ if err != nil {
+ log.Fatalf("Error starting metrics cleanup system: %v", err)
+ }
+ defer cleanupCron.Stop()
+
+ app := fiber.New()
+
+ app.Use(cors.New(cors.Config{
+ AllowOrigins: "*",
+ AllowHeaders: "Origin, Content-Type, Accept, Authorization",
+ }))
+
+ app.Get("/health", func(c *fiber.Ctx) error {
+ return c.JSON(fiber.Map{
+ "status": "ok",
+ })
+ })
+
+ app.Use(func(c *fiber.Ctx) error {
+ if c.Path() == "/health" {
+ return c.Next()
+ }
+ return middleware.AuthMiddleware()(c)
+ })
+
+ app.Get("/metrics", func(c *fiber.Ctx) error {
+ limit := c.Query("limit", "50")
+
+ var metrics []monitoring.SystemMetrics
+ if limit == "all" {
+ dbMetrics, err := db.GetAllMetrics()
+ if err != nil {
+ return c.Status(500).JSON(fiber.Map{
+ "error": "Failed to fetch metrics",
+ })
+ }
+ for _, m := range dbMetrics {
+ metrics = append(metrics, monitoring.ConvertToSystemMetrics(m))
+ }
+ } else {
+ n, err := strconv.Atoi(limit)
+ if err != nil {
+ n = 50
+ }
+ dbMetrics, err := db.GetLastNMetrics(n)
+ if err != nil {
+ return c.Status(500).JSON(fiber.Map{
+ "error": "Failed to fetch metrics",
+ })
+ }
+ for _, m := range dbMetrics {
+ metrics = append(metrics, monitoring.ConvertToSystemMetrics(m))
+ }
+ }
+
+ return c.JSON(metrics)
+ })
+
+ containerMonitor, err := containers.NewContainerMonitor(db)
+ if err != nil {
+ log.Fatalf("Failed to create container monitor: %v", err)
+ }
+ if err := containerMonitor.Start(); err != nil {
+ log.Fatalf("Failed to start container monitor: %v", err)
+ }
+ defer containerMonitor.Stop()
+
+ app.Get("/metrics/containers", func(c *fiber.Ctx) error {
+ limit := c.Query("limit", "50")
+ appName := c.Query("appName", "")
+
+ if appName == "" {
+ return c.JSON([]database.ContainerMetric{})
+ }
+
+ var metrics []database.ContainerMetric
+ var err error
+
+ if limit == "all" {
+ metrics, err = db.GetAllMetricsContainer(appName)
+ } else {
+ limitNum, parseErr := strconv.Atoi(limit)
+ if parseErr != nil {
+ limitNum = 50
+ }
+ metrics, err = db.GetLastNContainerMetrics(appName, limitNum)
+ }
+
+ if err != nil {
+ return c.Status(500).JSON(fiber.Map{
+ "error": "Error getting container metrics: " + err.Error(),
+ })
+ }
+
+ return c.JSON(metrics)
+ })
+
+ go func() {
+ refreshRate := cfg.Server.RefreshRate
+ duration := time.Duration(refreshRate) * time.Second
+
+ log.Printf("Refreshing server metrics every %v", duration)
+ ticker := time.NewTicker(duration)
+ defer ticker.Stop()
+
+ for range ticker.C {
+ metrics := monitoring.GetServerMetrics()
+ if err := db.SaveMetric(metrics); err != nil {
+ log.Printf("Error saving metrics: %v", err)
+ }
+
+ if err := monitoring.CheckThresholds(metrics); err != nil {
+ log.Printf("Error checking thresholds: %v", err)
+ }
+ }
+ }()
+
+ port := cfg.Server.Port
+ if port == 0 {
+ port = 3001
+ }
+
+ log.Printf("Server starting on port %d", port)
+ log.Fatal(app.Listen(":" + strconv.Itoa(port)))
+}
diff --git a/apps/monitoring/middleware/auth.go b/apps/monitoring/middleware/auth.go
new file mode 100644
index 000000000..95448213a
--- /dev/null
+++ b/apps/monitoring/middleware/auth.go
@@ -0,0 +1,39 @@
+package middleware
+
+import (
+ "strings"
+
+ "github.com/gofiber/fiber/v2"
+ "github.com/mauriciogm/dokploy/apps/monitoring/config"
+)
+
+func AuthMiddleware() fiber.Handler {
+ return func(c *fiber.Ctx) error {
+ expectedToken := config.GetMetricsConfig().Server.Token
+
+ authHeader := c.Get("Authorization")
+ if authHeader == "" {
+ return c.Status(401).JSON(fiber.Map{
+ "error": "Authorization header is required",
+ })
+ }
+
+ // Check if the header starts with "Bearer "
+ if !strings.HasPrefix(authHeader, "Bearer ") {
+ return c.Status(401).JSON(fiber.Map{
+ "error": "Invalid authorization format. Use 'Bearer TOKEN'",
+ })
+ }
+
+ // Extract the token
+ token := strings.TrimPrefix(authHeader, "Bearer ")
+
+ if token != expectedToken {
+ return c.Status(401).JSON(fiber.Map{
+ "error": "Invalid token",
+ })
+ }
+
+ return c.Next()
+ }
+}
diff --git a/apps/monitoring/monitoring/monitor.go b/apps/monitoring/monitoring/monitor.go
new file mode 100644
index 000000000..0beb4320f
--- /dev/null
+++ b/apps/monitoring/monitoring/monitor.go
@@ -0,0 +1,261 @@
+package monitoring
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "os/exec"
+ "runtime"
+ "strings"
+ "time"
+
+ "github.com/shirou/gopsutil/v3/cpu"
+ "github.com/shirou/gopsutil/v3/disk"
+ "github.com/shirou/gopsutil/v3/host"
+ "github.com/shirou/gopsutil/v3/mem"
+ "github.com/shirou/gopsutil/v3/net"
+
+ "github.com/mauriciogm/dokploy/apps/monitoring/config"
+ "github.com/mauriciogm/dokploy/apps/monitoring/database"
+)
+
+type SystemMetrics struct {
+ CPU string `json:"cpu"`
+ CPUModel string `json:"cpuModel"`
+ CPUCores int32 `json:"cpuCores"`
+ CPUPhysicalCores int32 `json:"cpuPhysicalCores"`
+ CPUSpeed float64 `json:"cpuSpeed"`
+ OS string `json:"os"`
+ Distro string `json:"distro"`
+ Kernel string `json:"kernel"`
+ Arch string `json:"arch"`
+ MemUsed string `json:"memUsed"`
+ MemUsedGB string `json:"memUsedGB"`
+ MemTotal string `json:"memTotal"`
+ Uptime uint64 `json:"uptime"`
+ DiskUsed string `json:"diskUsed"`
+ TotalDisk string `json:"totalDisk"`
+ NetworkIn string `json:"networkIn"`
+ NetworkOut string `json:"networkOut"`
+ Timestamp string `json:"timestamp"`
+}
+
+type AlertPayload struct {
+ ServerType string `json:"ServerType"`
+ Type string `json:"Type"`
+ Value float64 `json:"Value"`
+ Threshold float64 `json:"Threshold"`
+ Message string `json:"Message"`
+ Timestamp string `json:"Timestamp"`
+ Token string `json:"Token"`
+}
+
+func getRealOS() string {
+ if content, err := os.ReadFile("/etc/os-release"); err == nil {
+ lines := strings.Split(string(content), "\n")
+ var id, name, version string
+ for _, line := range lines {
+ if strings.HasPrefix(line, "PRETTY_NAME=") {
+ return strings.Trim(strings.TrimPrefix(line, "PRETTY_NAME="), "\"")
+ } else if strings.HasPrefix(line, "NAME=") {
+ name = strings.Trim(strings.TrimPrefix(line, "NAME="), "\"")
+ } else if strings.HasPrefix(line, "VERSION=") {
+ version = strings.Trim(strings.TrimPrefix(line, "VERSION="), "\"")
+ } else if strings.HasPrefix(line, "ID=") {
+ id = strings.Trim(strings.TrimPrefix(line, "ID="), "\"")
+ }
+ }
+ if name != "" && version != "" {
+ return fmt.Sprintf("%s %s", name, version)
+ }
+ if name != "" {
+ return name
+ }
+ if id != "" {
+ return id
+ }
+ }
+
+ if content, err := os.ReadFile("/etc/system-release"); err == nil {
+ text := strings.ToLower(string(content))
+ switch {
+ case strings.Contains(text, "red hat"):
+ return "rhel"
+ case strings.Contains(text, "centos"):
+ return "centos"
+ case strings.Contains(text, "fedora"):
+ return "fedora"
+ }
+ }
+
+ cmd := exec.Command("uname", "-a")
+ if output, err := cmd.Output(); err == nil {
+ osInfo := strings.ToLower(string(output))
+ switch {
+ case strings.Contains(osInfo, "debian"):
+ return "debian"
+ case strings.Contains(osInfo, "ubuntu"):
+ return "ubuntu"
+ case strings.Contains(osInfo, "centos"):
+ return "centos"
+ case strings.Contains(osInfo, "fedora"):
+ return "fedora"
+ case strings.Contains(osInfo, "red hat"):
+ return "rhel"
+ case strings.Contains(osInfo, "arch"):
+ return "arch"
+ case strings.Contains(osInfo, "darwin"):
+ return "darwin"
+ }
+ }
+
+ return runtime.GOOS
+}
+
+func GetServerMetrics() database.ServerMetric {
+ v, _ := mem.VirtualMemory()
+ c, _ := cpu.Percent(0, false)
+ cpuInfo, _ := cpu.Info()
+ diskInfo, _ := disk.Usage("/")
+ netInfo, _ := net.IOCounters(false)
+ hostInfo, _ := host.Info()
+ distro := getRealOS()
+
+ cpuModel := ""
+ if len(cpuInfo) > 0 {
+ cpuModel = fmt.Sprintf("%s %s", cpuInfo[0].VendorID, cpuInfo[0].ModelName)
+ }
+
+ memTotalGB := float64(v.Total) / 1024 / 1024 / 1024
+ memUsedGB := float64(v.Used) / 1024 / 1024 / 1024
+ memUsedPercent := (memUsedGB / memTotalGB) * 100
+
+ var networkIn, networkOut float64
+ if len(netInfo) > 0 {
+ networkIn = float64(netInfo[0].BytesRecv) / 1024 / 1024
+ networkOut = float64(netInfo[0].BytesSent) / 1024 / 1024
+ }
+ return database.ServerMetric{
+ Timestamp: time.Now().UTC().Format(time.RFC3339Nano),
+ CPU: c[0],
+ CPUModel: cpuModel,
+ CPUCores: int32(runtime.NumCPU()),
+ CPUPhysicalCores: int32(len(cpuInfo)),
+ CPUSpeed: float64(cpuInfo[0].Mhz),
+ OS: getRealOS(),
+ Distro: distro,
+ Kernel: hostInfo.KernelVersion,
+ Arch: hostInfo.KernelArch,
+ MemUsed: memUsedPercent,
+ MemUsedGB: memUsedGB,
+ MemTotal: memTotalGB,
+ Uptime: hostInfo.Uptime,
+ DiskUsed: float64(diskInfo.UsedPercent),
+ TotalDisk: float64(diskInfo.Total) / 1024 / 1024 / 1024,
+ NetworkIn: networkIn,
+ NetworkOut: networkOut,
+ }
+}
+
+func ConvertToSystemMetrics(metric database.ServerMetric) SystemMetrics {
+ return SystemMetrics{
+ CPU: fmt.Sprintf("%.2f", metric.CPU),
+ CPUModel: metric.CPUModel,
+ CPUCores: metric.CPUCores,
+ CPUPhysicalCores: metric.CPUPhysicalCores,
+ CPUSpeed: metric.CPUSpeed,
+ OS: metric.OS,
+ Distro: metric.Distro,
+ Kernel: metric.Kernel,
+ Arch: metric.Arch,
+ MemUsed: fmt.Sprintf("%.2f", metric.MemUsed),
+ MemUsedGB: fmt.Sprintf("%.2f", metric.MemUsedGB),
+ MemTotal: fmt.Sprintf("%.2f", metric.MemTotal),
+ Uptime: metric.Uptime,
+ DiskUsed: fmt.Sprintf("%.2f", metric.DiskUsed),
+ TotalDisk: fmt.Sprintf("%.2f", metric.TotalDisk),
+ NetworkIn: fmt.Sprintf("%.2f", metric.NetworkIn),
+ NetworkOut: fmt.Sprintf("%.2f", metric.NetworkOut),
+ Timestamp: metric.Timestamp,
+ }
+}
+
+func CheckThresholds(metrics database.ServerMetric) error {
+ cfg := config.GetMetricsConfig()
+ cpuThreshold := float64(cfg.Server.Thresholds.CPU)
+ memThreshold := float64(cfg.Server.Thresholds.Memory)
+ callbackURL := cfg.Server.UrlCallback
+ metricsToken := cfg.Server.Token
+
+ // log.Printf("CPU threshold: %.2f%%", cpuThreshold)
+ // log.Printf("Current CPU usage: %.2f%%", metrics.CPU)
+ // log.Printf("Memory threshold: %.2f%%", memThreshold)
+ // log.Printf("Callback URL: %s", callbackURL)
+ // log.Printf("Metrics token: %s", metricsToken)
+
+ if cpuThreshold == 0 && memThreshold == 0 {
+ return nil
+ }
+
+ if cpuThreshold > 0 && metrics.CPU > cpuThreshold {
+ alert := AlertPayload{
+ ServerType: cfg.Server.ServerType,
+ Type: "CPU",
+ Value: metrics.CPU,
+ Threshold: cpuThreshold,
+ Message: fmt.Sprintf("CPU usage (%.2f%%) exceeded threshold (%.2f%%)", metrics.CPU, cpuThreshold),
+ Timestamp: metrics.Timestamp,
+ Token: metricsToken,
+ }
+ if err := sendAlert(callbackURL, alert); err != nil {
+ return fmt.Errorf("failed to send CPU alert: %v", err)
+ }
+ }
+
+ if memThreshold > 0 && metrics.MemUsed > memThreshold {
+ alert := AlertPayload{
+ ServerType: cfg.Server.ServerType,
+ Type: "Memory",
+ Value: metrics.MemUsed,
+ Threshold: memThreshold,
+ Message: fmt.Sprintf("Memory usage (%.2f%%) exceeded threshold (%.2f%%)", metrics.MemUsed, memThreshold),
+ Timestamp: metrics.Timestamp,
+ Token: metricsToken,
+ }
+ if err := sendAlert(callbackURL, alert); err != nil {
+ return fmt.Errorf("failed to send memory alert: %v", err)
+ }
+ }
+
+ return nil
+}
+
+func sendAlert(callbackURL string, payload AlertPayload) error {
+ if callbackURL == "" {
+ return fmt.Errorf("callback URL is not set")
+ }
+ wrappedPayload := map[string]interface{}{
+ "json": payload,
+ }
+
+ jsonData, err := json.Marshal(wrappedPayload)
+ if err != nil {
+ return fmt.Errorf("failed to marshal alert payload: %v", err)
+ }
+
+ resp, err := http.Post(callbackURL, "application/json", bytes.NewBuffer(jsonData))
+ if err != nil {
+ return fmt.Errorf("failed to send POST request: %v", err)
+ }
+ defer resp.Body.Close()
+
+ if resp.StatusCode != http.StatusOK {
+ bodyBytes, _ := io.ReadAll(resp.Body)
+ return fmt.Errorf("received non-OK response status: %s, body: %s", resp.Status, string(bodyBytes))
+ }
+
+ return nil
+}
diff --git a/apps/schedules/package.json b/apps/schedules/package.json
index dd6818d1f..0a869fb07 100644
--- a/apps/schedules/package.json
+++ b/apps/schedules/package.json
@@ -8,7 +8,7 @@
"typecheck": "tsc --noEmit"
},
"dependencies": {
- "drizzle-orm": "^0.30.8",
+ "drizzle-orm": "^0.39.1",
"ioredis": "5.4.1",
"bullmq": "5.4.2",
"@hono/zod-validator": "0.3.0",
diff --git a/apps/schedules/tsconfig.json b/apps/schedules/tsconfig.json
index 3c0b02bc0..3d4adb168 100644
--- a/apps/schedules/tsconfig.json
+++ b/apps/schedules/tsconfig.json
@@ -7,7 +7,8 @@
"skipLibCheck": true,
"outDir": "dist",
"jsx": "react-jsx",
- "jsxImportSource": "hono/jsx"
+ "jsxImportSource": "hono/jsx",
+ "declaration": false
},
"exclude": ["node_modules", "dist"]
}
diff --git a/biome.json b/biome.json
index f5a6c2328..cf677ec40 100644
--- a/biome.json
+++ b/biome.json
@@ -24,7 +24,10 @@
},
"correctness": {
"useExhaustiveDependencies": "off",
- "noUnsafeOptionalChaining": "off"
+ "noUnsafeOptionalChaining": "off",
+ "noUnusedImports": "error",
+ "noUnusedFunctionParameters": "error",
+ "noUnusedVariables": "error"
},
"style": {
"noNonNullAssertion": "off"
diff --git a/lefthook.yml b/lefthook.yml
index 1a491cd8a..3f5a6d09f 100644
--- a/lefthook.yml
+++ b/lefthook.yml
@@ -37,9 +37,9 @@
commit-msg:
commands:
commitlint:
- run: "npx commitlint --edit $1"
+ # run: "npx commitlint --edit $1"
pre-commit:
commands:
check:
- run: "pnpm check"
+ # run: "pnpm check"
diff --git a/packages/server/auth-schema.ts b/packages/server/auth-schema.ts
new file mode 100644
index 000000000..a58290467
--- /dev/null
+++ b/packages/server/auth-schema.ts
@@ -0,0 +1,133 @@
+// import {
+// pgTable,
+// text,
+// integer,
+// timestamp,
+// boolean,
+// } from "drizzle-orm/pg-core";
+
+// export const users_temp = pgTable("users_temp", {
+// id: text("id").primaryKey(),
+// name: text("name").notNull(),
+// email: text("email").notNull().unique(),
+// emailVerified: boolean("email_verified").notNull(),
+// image: text("image"),
+// createdAt: timestamp("created_at").notNull(),
+// updatedAt: timestamp("updated_at").notNull(),
+// twoFactorEnabled: boolean("two_factor_enabled"),
+// role: text("role"),
+// ownerId: text("owner_id"),
+// });
+
+// export const session = pgTable("session", {
+// id: text("id").primaryKey(),
+// expiresAt: timestamp("expires_at").notNull(),
+// token: text("token").notNull().unique(),
+// createdAt: timestamp("created_at").notNull(),
+// updatedAt: timestamp("updated_at").notNull(),
+// ipAddress: text("ip_address"),
+// userAgent: text("user_agent"),
+// userId: text("user_id")
+// .notNull()
+// .references(() => users_temp.id, { onDelete: "cascade" }),
+// activeOrganizationId: text("active_organization_id"),
+// });
+
+// export const account = pgTable("account", {
+// id: text("id").primaryKey(),
+// accountId: text("account_id").notNull(),
+// providerId: text("provider_id").notNull(),
+// userId: text("user_id")
+// .notNull()
+// .references(() => users_temp.id, { onDelete: "cascade" }),
+// accessToken: text("access_token"),
+// refreshToken: text("refresh_token"),
+// idToken: text("id_token"),
+// accessTokenExpiresAt: timestamp("access_token_expires_at"),
+// refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
+// scope: text("scope"),
+// password: text("password"),
+// createdAt: timestamp("created_at").notNull(),
+// updatedAt: timestamp("updated_at").notNull(),
+// });
+
+// export const verification = pgTable("verification", {
+// id: text("id").primaryKey(),
+// identifier: text("identifier").notNull(),
+// value: text("value").notNull(),
+// expiresAt: timestamp("expires_at").notNull(),
+// createdAt: timestamp("created_at"),
+// updatedAt: timestamp("updated_at"),
+// });
+
+// export const apikey = pgTable("apikey", {
+// id: text("id").primaryKey(),
+// name: text("name"),
+// start: text("start"),
+// prefix: text("prefix"),
+// key: text("key").notNull(),
+// userId: text("user_id")
+// .notNull()
+// .references(() => user.id, { onDelete: "cascade" }),
+// refillInterval: integer("refill_interval"),
+// refillAmount: integer("refill_amount"),
+// lastRefillAt: timestamp("last_refill_at"),
+// enabled: boolean("enabled"),
+// rateLimitEnabled: boolean("rate_limit_enabled"),
+// rateLimitTimeWindow: integer("rate_limit_time_window"),
+// rateLimitMax: integer("rate_limit_max"),
+// requestCount: integer("request_count"),
+// remaining: integer("remaining"),
+// lastRequest: timestamp("last_request"),
+// expiresAt: timestamp("expires_at"),
+// createdAt: timestamp("created_at").notNull(),
+// updatedAt: timestamp("updated_at").notNull(),
+// permissions: text("permissions"),
+// metadata: text("metadata"),
+// });
+
+// export const twoFactor = pgTable("two_factor", {
+// id: text("id").primaryKey(),
+// secret: text("secret").notNull(),
+// backupCodes: text("backup_codes").notNull(),
+// userId: text("user_id")
+// .notNull()
+// .references(() => user.id, { onDelete: "cascade" }),
+// });
+
+// export const organization = pgTable("organization", {
+// id: text("id").primaryKey(),
+// name: text("name").notNull(),
+// slug: text("slug").unique(),
+// logo: text("logo"),
+// createdAt: timestamp("created_at").notNull(),
+// metadata: text("metadata"),
+// });
+
+// export const member = pgTable("member", {
+// id: text("id").primaryKey(),
+// organizationId: text("organization_id")
+// .notNull()
+// .references(() => organization.id, { onDelete: "cascade" }),
+// userId: text("user_id")
+// .notNull()
+// .references(() => user.id, { onDelete: "cascade" }),
+// role: text("role").notNull(),
+// teamId: text("team_id"),
+// createdAt: timestamp("created_at").notNull(),
+// });
+
+// export const invitation = pgTable("invitation", {
+// id: text("id").primaryKey(),
+// organizationId: text("organization_id")
+// .notNull()
+// .references(() => organization.id, { onDelete: "cascade" }),
+// email: text("email").notNull(),
+// role: text("role"),
+// teamId: text("team_id"),
+// status: text("status").notNull(),
+// expiresAt: timestamp("expires_at").notNull(),
+// inviterId: text("inviter_id")
+// .notNull()
+// .references(() => user.id, { onDelete: "cascade" }),
+// });
diff --git a/packages/server/package.json b/packages/server/package.json
index a57ff65e3..3265c95cf 100644
--- a/packages/server/package.json
+++ b/packages/server/package.json
@@ -35,6 +35,11 @@
"@ai-sdk/mistral": "^1.0.6",
"@ai-sdk/openai": "^1.0.12",
"@ai-sdk/openai-compatible": "^0.0.13",
+ "@better-auth/utils":"0.2.3",
+ "@oslojs/encoding":"1.1.0",
+ "@oslojs/crypto":"1.0.1",
+ "drizzle-dbml-generator":"0.10.0",
+ "better-auth":"1.2.0",
"@faker-js/faker": "^8.4.1",
"@lucia-auth/adapter-drizzle": "1.0.7",
"@octokit/auth-app": "^6.0.4",
@@ -48,7 +53,7 @@
"date-fns": "3.6.0",
"dockerode": "4.0.2",
"dotenv": "16.4.5",
- "drizzle-orm": "^0.30.8",
+ "drizzle-orm": "^0.39.1",
"drizzle-zod": "0.5.1",
"hi-base32": "^0.5.1",
"js-yaml": "4.1.0",
@@ -88,7 +93,7 @@
"@types/react-dom": "^18.2.15",
"@types/ssh2": "1.15.1",
"@types/ws": "8.5.10",
- "drizzle-kit": "^0.21.1",
+ "drizzle-kit": "^0.30.4",
"esbuild": "0.20.2",
"esbuild-plugin-alias": "0.2.1",
"postcss": "^8.4.31",
diff --git a/packages/server/src/auth/auth.ts b/packages/server/src/auth/auth.ts
deleted file mode 100644
index ab340d0af..000000000
--- a/packages/server/src/auth/auth.ts
+++ /dev/null
@@ -1,117 +0,0 @@
-import type { IncomingMessage, ServerResponse } from "node:http";
-import { findAdminByAuthId } from "@dokploy/server/services/admin";
-import { findUserByAuthId } from "@dokploy/server/services/user";
-import { DrizzlePostgreSQLAdapter } from "@lucia-auth/adapter-drizzle";
-import { TimeSpan } from "lucia";
-import { Lucia } from "lucia/dist/core.js";
-import type { Session, User } from "lucia/dist/core.js";
-import { db } from "../db";
-import { type DatabaseUser, auth, sessionTable } from "../db/schema";
-
-export const adapter = new DrizzlePostgreSQLAdapter(db, sessionTable, auth);
-
-export const lucia = new Lucia(adapter, {
- sessionCookie: {
- attributes: {
- secure: false,
- },
- },
- sessionExpiresIn: new TimeSpan(1, "d"),
- getUserAttributes: (attributes) => {
- return {
- email: attributes.email,
- rol: attributes.rol,
- secret: attributes.secret !== null,
- adminId: attributes.adminId,
- };
- },
-});
-
-declare module "lucia" {
- interface Register {
- Lucia: typeof lucia;
- DatabaseUserAttributes: Omit & {
- authId: string;
- adminId: string;
- };
- }
-}
-
-export type ReturnValidateToken = Promise<{
- user: (User & { authId: string; adminId: string }) | null;
- session: Session | null;
-}>;
-
-export async function validateRequest(
- req: IncomingMessage,
- res: ServerResponse,
-): ReturnValidateToken {
- const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
-
- if (!sessionId) {
- return {
- user: null,
- session: null,
- };
- }
- const result = await lucia.validateSession(sessionId);
- if (result?.session?.fresh) {
- res.appendHeader(
- "Set-Cookie",
- lucia.createSessionCookie(result.session.id).serialize(),
- );
- }
- if (!result.session) {
- res.appendHeader(
- "Set-Cookie",
- lucia.createBlankSessionCookie().serialize(),
- );
- }
- if (result.user) {
- try {
- if (result.user?.rol === "admin") {
- const admin = await findAdminByAuthId(result.user.id);
- result.user.adminId = admin.adminId;
- } else if (result.user?.rol === "user") {
- const userResult = await findUserByAuthId(result.user.id);
- result.user.adminId = userResult.adminId;
- }
- } catch (error) {
- return {
- user: null,
- session: null,
- };
- }
- }
-
- return {
- session: result.session,
- ...((result.user && {
- user: {
- authId: result.user.id,
- email: result.user.email,
- rol: result.user.rol,
- id: result.user.id,
- secret: result.user.secret,
- adminId: result.user.adminId,
- },
- }) || {
- user: null,
- }),
- };
-}
-
-export async function validateWebSocketRequest(
- req: IncomingMessage,
-): Promise<{ user: User; session: Session } | { user: null; session: null }> {
- const sessionId = lucia.readSessionCookie(req.headers.cookie ?? "");
-
- if (!sessionId) {
- return {
- user: null,
- session: null,
- };
- }
- const result = await lucia.validateSession(sessionId);
- return result;
-}
diff --git a/packages/server/src/auth/token.ts b/packages/server/src/auth/token.ts
deleted file mode 100644
index f29d4dbdd..000000000
--- a/packages/server/src/auth/token.ts
+++ /dev/null
@@ -1,99 +0,0 @@
-import type { IncomingMessage } from "node:http";
-import { TimeSpan } from "lucia";
-import { Lucia } from "lucia/dist/core.js";
-import { findAdminByAuthId } from "../services/admin";
-import { findUserByAuthId } from "../services/user";
-import { type ReturnValidateToken, adapter } from "./auth";
-
-export const luciaToken = new Lucia(adapter, {
- sessionCookie: {
- attributes: {
- secure: false,
- },
- },
- sessionExpiresIn: new TimeSpan(365, "d"),
- getUserAttributes: (attributes) => {
- return {
- email: attributes.email,
- rol: attributes.rol,
- secret: attributes.secret !== null,
- };
- },
-});
-
-export const validateBearerToken = async (
- req: IncomingMessage,
-): ReturnValidateToken => {
- const authorizationHeader = req.headers.authorization;
- const sessionId = luciaToken.readBearerToken(authorizationHeader ?? "");
- if (!sessionId) {
- return {
- user: null,
- session: null,
- };
- }
- const result = await luciaToken.validateSession(sessionId);
-
- if (result.user) {
- if (result.user?.rol === "admin") {
- const admin = await findAdminByAuthId(result.user.id);
- result.user.adminId = admin.adminId;
- } else if (result.user?.rol === "user") {
- const userResult = await findUserByAuthId(result.user.id);
- result.user.adminId = userResult.adminId;
- }
- }
- return {
- session: result.session,
- ...((result.user && {
- user: {
- adminId: result.user.adminId,
- authId: result.user.id,
- email: result.user.email,
- rol: result.user.rol,
- id: result.user.id,
- secret: result.user.secret,
- },
- }) || {
- user: null,
- }),
- };
-};
-
-export const validateBearerTokenAPI = async (
- authorizationHeader: string,
-): ReturnValidateToken => {
- const sessionId = luciaToken.readBearerToken(authorizationHeader ?? "");
- if (!sessionId) {
- return {
- user: null,
- session: null,
- };
- }
- const result = await luciaToken.validateSession(sessionId);
-
- if (result.user) {
- if (result.user?.rol === "admin") {
- const admin = await findAdminByAuthId(result.user.id);
- result.user.adminId = admin.adminId;
- } else if (result.user?.rol === "user") {
- const userResult = await findUserByAuthId(result.user.id);
- result.user.adminId = userResult.adminId;
- }
- }
- return {
- session: result.session,
- ...((result.user && {
- user: {
- adminId: result.user.adminId,
- authId: result.user.id,
- email: result.user.email,
- rol: result.user.rol,
- id: result.user.id,
- secret: result.user.secret,
- },
- }) || {
- user: null,
- }),
- };
-};
diff --git a/packages/server/src/db/schema/account.ts b/packages/server/src/db/schema/account.ts
new file mode 100644
index 000000000..8291ea4d6
--- /dev/null
+++ b/packages/server/src/db/schema/account.ts
@@ -0,0 +1,194 @@
+import { relations, sql } from "drizzle-orm";
+import {
+ boolean,
+ integer,
+ pgTable,
+ text,
+ timestamp,
+} from "drizzle-orm/pg-core";
+import { nanoid } from "nanoid";
+import { projects } from "./project";
+import { server } from "./server";
+import { users_temp } from "./user";
+
+export const account = pgTable("account", {
+ id: text("id")
+ .primaryKey()
+ .$defaultFn(() => nanoid()),
+ accountId: text("account_id")
+ .notNull()
+ .$defaultFn(() => nanoid()),
+ providerId: text("provider_id").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+ accessToken: text("access_token"),
+ refreshToken: text("refresh_token"),
+ idToken: text("id_token"),
+ accessTokenExpiresAt: timestamp("access_token_expires_at"),
+ refreshTokenExpiresAt: timestamp("refresh_token_expires_at"),
+ scope: text("scope"),
+ password: text("password"),
+ is2FAEnabled: boolean("is2FAEnabled").notNull().default(false),
+ createdAt: timestamp("created_at").notNull(),
+ updatedAt: timestamp("updated_at").notNull(),
+ resetPasswordToken: text("resetPasswordToken"),
+ resetPasswordExpiresAt: text("resetPasswordExpiresAt"),
+ confirmationToken: text("confirmationToken"),
+ confirmationExpiresAt: text("confirmationExpiresAt"),
+});
+
+export const accountRelations = relations(account, ({ one }) => ({
+ user: one(users_temp, {
+ fields: [account.userId],
+ references: [users_temp.id],
+ }),
+}));
+
+export const verification = pgTable("verification", {
+ id: text("id").primaryKey(),
+ identifier: text("identifier").notNull(),
+ value: text("value").notNull(),
+ expiresAt: timestamp("expires_at").notNull(),
+ createdAt: timestamp("created_at"),
+ updatedAt: timestamp("updated_at"),
+});
+
+export const organization = pgTable("organization", {
+ id: text("id")
+ .primaryKey()
+ .$defaultFn(() => nanoid()),
+ name: text("name").notNull(),
+ slug: text("slug").unique(),
+ logo: text("logo"),
+ createdAt: timestamp("created_at").notNull(),
+ metadata: text("metadata"),
+ ownerId: text("owner_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+});
+
+export const organizationRelations = relations(
+ organization,
+ ({ one, many }) => ({
+ owner: one(users_temp, {
+ fields: [organization.ownerId],
+ references: [users_temp.id],
+ }),
+ servers: many(server),
+ projects: many(projects),
+ members: many(member),
+ }),
+);
+
+export const member = pgTable("member", {
+ id: text("id")
+ .primaryKey()
+ .$defaultFn(() => nanoid()),
+ organizationId: text("organization_id")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+ role: text("role").notNull().$type<"owner" | "member" | "admin">(),
+ createdAt: timestamp("created_at").notNull(),
+ teamId: text("team_id"),
+ // Permissions
+ canCreateProjects: boolean("canCreateProjects").notNull().default(false),
+ canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
+ canCreateServices: boolean("canCreateServices").notNull().default(false),
+ canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
+ canDeleteServices: boolean("canDeleteServices").notNull().default(false),
+ canAccessToDocker: boolean("canAccessToDocker").notNull().default(false),
+ canAccessToAPI: boolean("canAccessToAPI").notNull().default(false),
+ canAccessToGitProviders: boolean("canAccessToGitProviders")
+ .notNull()
+ .default(false),
+ canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
+ .notNull()
+ .default(false),
+ accessedProjects: text("accesedProjects")
+ .array()
+ .notNull()
+ .default(sql`ARRAY[]::text[]`),
+ accessedServices: text("accesedServices")
+ .array()
+ .notNull()
+ .default(sql`ARRAY[]::text[]`),
+});
+
+export const memberRelations = relations(member, ({ one }) => ({
+ organization: one(organization, {
+ fields: [member.organizationId],
+ references: [organization.id],
+ }),
+ user: one(users_temp, {
+ fields: [member.userId],
+ references: [users_temp.id],
+ }),
+}));
+
+export const invitation = pgTable("invitation", {
+ id: text("id").primaryKey(),
+ organizationId: text("organization_id")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
+ email: text("email").notNull(),
+ role: text("role").$type<"owner" | "member" | "admin">(),
+ status: text("status").notNull(),
+ expiresAt: timestamp("expires_at").notNull(),
+ inviterId: text("inviter_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+ teamId: text("team_id"),
+});
+
+export const invitationRelations = relations(invitation, ({ one }) => ({
+ organization: one(organization, {
+ fields: [invitation.organizationId],
+ references: [organization.id],
+ }),
+}));
+
+export const twoFactor = pgTable("two_factor", {
+ id: text("id").primaryKey(),
+ secret: text("secret").notNull(),
+ backupCodes: text("backup_codes").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+});
+
+export const apikey = pgTable("apikey", {
+ id: text("id").primaryKey(),
+ name: text("name"),
+ start: text("start"),
+ prefix: text("prefix"),
+ key: text("key").notNull(),
+ userId: text("user_id")
+ .notNull()
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+ refillInterval: integer("refill_interval"),
+ refillAmount: integer("refill_amount"),
+ lastRefillAt: timestamp("last_refill_at"),
+ enabled: boolean("enabled"),
+ rateLimitEnabled: boolean("rate_limit_enabled"),
+ rateLimitTimeWindow: integer("rate_limit_time_window"),
+ rateLimitMax: integer("rate_limit_max"),
+ requestCount: integer("request_count"),
+ remaining: integer("remaining"),
+ lastRequest: timestamp("last_request"),
+ expiresAt: timestamp("expires_at"),
+ createdAt: timestamp("created_at").notNull(),
+ updatedAt: timestamp("updated_at").notNull(),
+ permissions: text("permissions"),
+ metadata: text("metadata"),
+});
+
+export const apikeyRelations = relations(apikey, ({ one }) => ({
+ user: one(users_temp, {
+ fields: [apikey.userId],
+ references: [users_temp.id],
+ }),
+}));
diff --git a/packages/server/src/db/schema/admin.ts b/packages/server/src/db/schema/admin.ts
deleted file mode 100644
index f5b46fdd4..000000000
--- a/packages/server/src/db/schema/admin.ts
+++ /dev/null
@@ -1,121 +0,0 @@
-import { relations } from "drizzle-orm";
-import { boolean, integer, pgTable, text } from "drizzle-orm/pg-core";
-import { createInsertSchema } from "drizzle-zod";
-import { nanoid } from "nanoid";
-import { z } from "zod";
-import { ai } from "./ai";
-import { auth } from "./auth";
-import { certificates } from "./certificate";
-import { registry } from "./registry";
-import { certificateType } from "./shared";
-import { sshKeys } from "./ssh-key";
-import { users } from "./user";
-
-export const admins = pgTable("admin", {
- adminId: text("adminId")
- .notNull()
- .primaryKey()
- .$defaultFn(() => nanoid()),
- serverIp: text("serverIp"),
- certificateType: certificateType("certificateType").notNull().default("none"),
- host: text("host"),
- letsEncryptEmail: text("letsEncryptEmail"),
- sshPrivateKey: text("sshPrivateKey"),
- enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
- enableLogRotation: boolean("enableLogRotation").notNull().default(false),
- authId: text("authId")
- .notNull()
- .references(() => auth.id, { onDelete: "cascade" }),
- createdAt: text("createdAt")
- .notNull()
- .$defaultFn(() => new Date().toISOString()),
- stripeCustomerId: text("stripeCustomerId"),
- stripeSubscriptionId: text("stripeSubscriptionId"),
- serversQuantity: integer("serversQuantity").notNull().default(0),
-});
-
-export const adminsRelations = relations(admins, ({ one, many }) => ({
- auth: one(auth, {
- fields: [admins.authId],
- references: [auth.id],
- }),
- users: many(users),
- registry: many(registry),
- sshKeys: many(sshKeys),
- certificates: many(certificates),
- ai: many(ai),
-}));
-
-const createSchema = createInsertSchema(admins, {
- adminId: z.string(),
- enableDockerCleanup: z.boolean().optional(),
- sshPrivateKey: z.string().optional(),
- certificateType: z.enum(["letsencrypt", "none"]).default("none"),
- serverIp: z.string().optional(),
- letsEncryptEmail: z.string().optional(),
-});
-
-export const apiUpdateAdmin = createSchema.partial();
-
-export const apiSaveSSHKey = createSchema
- .pick({
- sshPrivateKey: true,
- })
- .required();
-
-export const apiAssignDomain = createSchema
- .pick({
- host: true,
- certificateType: true,
- letsEncryptEmail: true,
- })
- .required()
- .partial({
- letsEncryptEmail: true,
- });
-
-export const apiUpdateDockerCleanup = createSchema
- .pick({
- enableDockerCleanup: true,
- })
- .required()
- .extend({
- serverId: z.string().optional(),
- });
-
-export const apiTraefikConfig = z.object({
- traefikConfig: z.string().min(1),
-});
-
-export const apiModifyTraefikConfig = z.object({
- path: z.string().min(1),
- traefikConfig: z.string().min(1),
- serverId: z.string().optional(),
-});
-export const apiReadTraefikConfig = z.object({
- path: z.string().min(1),
- serverId: z.string().optional(),
-});
-
-export const apiEnableDashboard = z.object({
- enableDashboard: z.boolean().optional(),
- serverId: z.string().optional(),
-});
-
-export const apiServerSchema = z
- .object({
- serverId: z.string().optional(),
- })
- .optional();
-
-export const apiReadStatsLogs = z.object({
- page: z
- .object({
- pageIndex: z.number(),
- pageSize: z.number(),
- })
- .optional(),
- status: z.string().array().optional(),
- search: z.string().optional(),
- sort: z.object({ id: z.string(), desc: z.boolean() }).optional(),
-});
diff --git a/packages/server/src/db/schema/application.ts b/packages/server/src/db/schema/application.ts
index 2437f59d6..e670e2e24 100644
--- a/packages/server/src/db/schema/application.ts
+++ b/packages/server/src/db/schema/application.ts
@@ -44,7 +44,6 @@ export const buildType = pgEnum("buildType", [
"static",
]);
-// TODO: refactor this types
export interface HealthCheckSwarm {
Test?: string[] | undefined;
Interval?: number | undefined;
diff --git a/packages/server/src/db/schema/auth.ts b/packages/server/src/db/schema/auth.ts
deleted file mode 100644
index 3e16c68ee..000000000
--- a/packages/server/src/db/schema/auth.ts
+++ /dev/null
@@ -1,130 +0,0 @@
-import { getRandomValues } from "node:crypto";
-import { relations } from "drizzle-orm";
-import { boolean, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
-import { createInsertSchema } from "drizzle-zod";
-import { nanoid } from "nanoid";
-import { z } from "zod";
-import { admins } from "./admin";
-import { users } from "./user";
-
-const randomImages = [
- "/avatars/avatar-1.png",
- "/avatars/avatar-2.png",
- "/avatars/avatar-3.png",
- "/avatars/avatar-4.png",
- "/avatars/avatar-5.png",
- "/avatars/avatar-6.png",
- "/avatars/avatar-7.png",
- "/avatars/avatar-8.png",
- "/avatars/avatar-9.png",
- "/avatars/avatar-10.png",
- "/avatars/avatar-11.png",
- "/avatars/avatar-12.png",
-];
-
-const generateRandomImage = () => {
- return (
- randomImages[
- // @ts-ignore
- getRandomValues(new Uint32Array(1))[0] % randomImages.length
- ] || "/avatars/avatar-1.png"
- );
-};
-export type DatabaseUser = typeof auth.$inferSelect;
-export const roles = pgEnum("Roles", ["admin", "user"]);
-
-export const auth = pgTable("auth", {
- id: text("id")
- .notNull()
- .primaryKey()
- .$defaultFn(() => nanoid()),
- email: text("email").notNull().unique(),
- password: text("password").notNull(),
- rol: roles("rol").notNull(),
- image: text("image").$defaultFn(() => generateRandomImage()),
- secret: text("secret"),
- token: text("token"),
- is2FAEnabled: boolean("is2FAEnabled").notNull().default(false),
- createdAt: text("createdAt")
- .notNull()
- .$defaultFn(() => new Date().toISOString()),
- resetPasswordToken: text("resetPasswordToken"),
- resetPasswordExpiresAt: text("resetPasswordExpiresAt"),
- confirmationToken: text("confirmationToken"),
- confirmationExpiresAt: text("confirmationExpiresAt"),
-});
-
-export const authRelations = relations(auth, ({ many }) => ({
- admins: many(admins),
- users: many(users),
-}));
-const createSchema = createInsertSchema(auth, {
- email: z.string().email(),
- password: z.string().min(8),
- rol: z.enum(["admin", "user"]),
- image: z.string().optional(),
-});
-
-export const apiCreateAdmin = createSchema.pick({
- email: true,
- password: true,
-});
-
-export const apiCreateUser = createSchema
- .pick({
- password: true,
- id: true,
- token: true,
- })
- .required()
- .extend({
- token: z.string().min(1),
- });
-
-export const apiLogin = createSchema
- .pick({
- email: true,
- password: true,
- })
- .required();
-
-export const apiUpdateAuth = createSchema.partial().extend({
- email: z.string().nullable(),
- password: z.string().nullable(),
- image: z.string().optional(),
- currentPassword: z.string().nullable(),
-});
-
-export const apiUpdateAuthByAdmin = createSchema.partial().extend({
- email: z.string().nullable(),
- password: z.string().nullable(),
- image: z.string().optional(),
- id: z.string().min(1),
-});
-
-export const apiFindOneAuth = createSchema
- .pick({
- id: true,
- })
- .required();
-
-export const apiVerify2FA = createSchema
- .extend({
- pin: z.string().min(6),
- secret: z.string().min(1),
- })
- .pick({
- pin: true,
- secret: true,
- })
- .required();
-
-export const apiVerifyLogin2FA = createSchema
- .extend({
- pin: z.string().min(6),
- })
- .pick({
- pin: true,
- id: true,
- })
- .required();
diff --git a/packages/server/src/db/schema/bitbucket.ts b/packages/server/src/db/schema/bitbucket.ts
index 393cb1e7d..0311202d7 100644
--- a/packages/server/src/db/schema/bitbucket.ts
+++ b/packages/server/src/db/schema/bitbucket.ts
@@ -61,5 +61,5 @@ export const apiUpdateBitbucket = createSchema.extend({
name: z.string().min(1),
bitbucketUsername: z.string().optional(),
bitbucketWorkspaceName: z.string().optional(),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
diff --git a/packages/server/src/db/schema/certificate.ts b/packages/server/src/db/schema/certificate.ts
index 1df61be86..bf72f7db3 100644
--- a/packages/server/src/db/schema/certificate.ts
+++ b/packages/server/src/db/schema/certificate.ts
@@ -3,7 +3,7 @@ import { boolean, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
import { server } from "./server";
import { generateAppName } from "./utils";
@@ -20,27 +20,24 @@ export const certificates = pgTable("certificate", {
.$defaultFn(() => generateAppName("certificate"))
.unique(),
autoRenew: boolean("autoRenew"),
- adminId: text("adminId").references(() => admins.adminId, {
- onDelete: "cascade",
- }),
+ organizationId: text("organizationId")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
serverId: text("serverId").references(() => server.serverId, {
onDelete: "cascade",
}),
});
-export const certificatesRelations = relations(
- certificates,
- ({ one, many }) => ({
- server: one(server, {
- fields: [certificates.serverId],
- references: [server.serverId],
- }),
- admin: one(admins, {
- fields: [certificates.adminId],
- references: [admins.adminId],
- }),
+export const certificatesRelations = relations(certificates, ({ one }) => ({
+ server: one(server, {
+ fields: [certificates.serverId],
+ references: [server.serverId],
}),
-);
+ organization: one(organization, {
+ fields: [certificates.organizationId],
+ references: [organization.id],
+ }),
+}));
export const apiCreateCertificate = createInsertSchema(certificates, {
name: z.string().min(1),
diff --git a/packages/server/src/db/schema/compose.ts b/packages/server/src/db/schema/compose.ts
index e0c4863b5..ca8344eec 100644
--- a/packages/server/src/db/schema/compose.ts
+++ b/packages/server/src/db/schema/compose.ts
@@ -69,6 +69,7 @@ export const compose = pgTable("compose", {
composePath: text("composePath").notNull().default("./docker-compose.yml"),
suffix: text("suffix").notNull().default(""),
randomize: boolean("randomize").notNull().default(false),
+ isolatedDeployment: boolean("isolatedDeployment").notNull().default(false),
composeStatus: applicationStatus("composeStatus").notNull().default("idle"),
projectId: text("projectId")
.notNull()
diff --git a/packages/server/src/db/schema/dbml.ts b/packages/server/src/db/schema/dbml.ts
new file mode 100644
index 000000000..72a758146
--- /dev/null
+++ b/packages/server/src/db/schema/dbml.ts
@@ -0,0 +1,7 @@
+import { pgGenerate } from "drizzle-dbml-generator"; // Using Postgres for this example
+import * as schema from "./index";
+
+const out = "./schema.dbml";
+const relational = true;
+
+pgGenerate({ schema, out, relational });
diff --git a/packages/server/src/db/schema/deployment.ts b/packages/server/src/db/schema/deployment.ts
index ccaf64661..4dfed76b6 100644
--- a/packages/server/src/db/schema/deployment.ts
+++ b/packages/server/src/db/schema/deployment.ts
@@ -1,4 +1,4 @@
-import { is, relations } from "drizzle-orm";
+import { relations } from "drizzle-orm";
import {
type AnyPgColumn,
boolean,
@@ -47,6 +47,7 @@ export const deployments = pgTable("deployment", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
+ errorMessage: text("errorMessage"),
});
export const deploymentsRelations = relations(deployments, ({ one }) => ({
diff --git a/packages/server/src/db/schema/destination.ts b/packages/server/src/db/schema/destination.ts
index 7d7be6141..0aeb14902 100644
--- a/packages/server/src/db/schema/destination.ts
+++ b/packages/server/src/db/schema/destination.ts
@@ -3,7 +3,7 @@ import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
import { backups } from "./backups";
export const destinations = pgTable("destination", {
@@ -17,20 +17,19 @@ export const destinations = pgTable("destination", {
secretAccessKey: text("secretAccessKey").notNull(),
bucket: text("bucket").notNull(),
region: text("region").notNull(),
- // maybe it can be null
endpoint: text("endpoint").notNull(),
- adminId: text("adminId")
+ organizationId: text("organizationId")
.notNull()
- .references(() => admins.adminId, { onDelete: "cascade" }),
+ .references(() => organization.id, { onDelete: "cascade" }),
});
export const destinationsRelations = relations(
destinations,
({ many, one }) => ({
backups: many(backups),
- admin: one(admins, {
- fields: [destinations.adminId],
- references: [admins.adminId],
+ organization: one(organization, {
+ fields: [destinations.organizationId],
+ references: [organization.id],
}),
}),
);
diff --git a/packages/server/src/db/schema/git-provider.ts b/packages/server/src/db/schema/git-provider.ts
index dbbfc183b..922307376 100644
--- a/packages/server/src/db/schema/git-provider.ts
+++ b/packages/server/src/db/schema/git-provider.ts
@@ -3,7 +3,7 @@ import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
import { bitbucket } from "./bitbucket";
import { github } from "./github";
import { gitlab } from "./gitlab";
@@ -24,12 +24,12 @@ export const gitProvider = pgTable("git_provider", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- adminId: text("adminId").references(() => admins.adminId, {
- onDelete: "cascade",
- }),
+ organizationId: text("organizationId")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
});
-export const gitProviderRelations = relations(gitProvider, ({ one, many }) => ({
+export const gitProviderRelations = relations(gitProvider, ({ one }) => ({
github: one(github, {
fields: [gitProvider.gitProviderId],
references: [github.gitProviderId],
@@ -42,9 +42,9 @@ export const gitProviderRelations = relations(gitProvider, ({ one, many }) => ({
fields: [gitProvider.gitProviderId],
references: [bitbucket.gitProviderId],
}),
- admin: one(admins, {
- fields: [gitProvider.adminId],
- references: [admins.adminId],
+ organization: one(organization, {
+ fields: [gitProvider.organizationId],
+ references: [organization.id],
}),
}));
diff --git a/packages/server/src/db/schema/index.ts b/packages/server/src/db/schema/index.ts
index 6d335632b..0b453facc 100644
--- a/packages/server/src/db/schema/index.ts
+++ b/packages/server/src/db/schema/index.ts
@@ -1,8 +1,6 @@
export * from "./application";
export * from "./postgres";
export * from "./user";
-export * from "./admin";
-export * from "./auth";
export * from "./project";
export * from "./domain";
export * from "./mariadb";
@@ -31,3 +29,4 @@ export * from "./server";
export * from "./utils";
export * from "./preview-deployments";
export * from "./ai";
+export * from "./account";
diff --git a/packages/server/src/db/schema/notification.ts b/packages/server/src/db/schema/notification.ts
index 12c7698e2..1c8a2d8f3 100644
--- a/packages/server/src/db/schema/notification.ts
+++ b/packages/server/src/db/schema/notification.ts
@@ -3,7 +3,7 @@ import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
export const notificationType = pgEnum("notificationType", [
"slack",
@@ -24,6 +24,7 @@ export const notifications = pgTable("notification", {
databaseBackup: boolean("databaseBackup").notNull().default(false),
dokployRestart: boolean("dokployRestart").notNull().default(false),
dockerCleanup: boolean("dockerCleanup").notNull().default(false),
+ serverThreshold: boolean("serverThreshold").notNull().default(false),
notificationType: notificationType("notificationType").notNull(),
createdAt: text("createdAt")
.notNull()
@@ -43,9 +44,9 @@ export const notifications = pgTable("notification", {
gotifyId: text("gotifyId").references(() => gotify.gotifyId, {
onDelete: "cascade",
}),
- adminId: text("adminId").references(() => admins.adminId, {
- onDelete: "cascade",
- }),
+ organizationId: text("organizationId")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
});
export const slack = pgTable("slack", {
@@ -64,6 +65,7 @@ export const telegram = pgTable("telegram", {
.$defaultFn(() => nanoid()),
botToken: text("botToken").notNull(),
chatId: text("chatId").notNull(),
+ messageThreadId: text("messageThreadId"),
});
export const discord = pgTable("discord", {
@@ -120,9 +122,9 @@ export const notificationsRelations = relations(notifications, ({ one }) => ({
fields: [notifications.gotifyId],
references: [gotify.gotifyId],
}),
- admin: one(admins, {
- fields: [notifications.adminId],
- references: [admins.adminId],
+ organization: one(organization, {
+ fields: [notifications.organizationId],
+ references: [organization.id],
}),
}));
@@ -136,6 +138,7 @@ export const apiCreateSlack = notificationsSchema
name: true,
appDeploy: true,
dockerCleanup: true,
+ serverThreshold: true,
})
.extend({
webhookUrl: z.string().min(1),
@@ -146,7 +149,7 @@ export const apiCreateSlack = notificationsSchema
export const apiUpdateSlack = apiCreateSlack.partial().extend({
notificationId: z.string().min(1),
slackId: z.string(),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
export const apiTestSlackConnection = apiCreateSlack.pick({
@@ -162,22 +165,25 @@ export const apiCreateTelegram = notificationsSchema
name: true,
appDeploy: true,
dockerCleanup: true,
+ serverThreshold: true,
})
.extend({
botToken: z.string().min(1),
chatId: z.string().min(1),
+ messageThreadId: z.string(),
})
.required();
export const apiUpdateTelegram = apiCreateTelegram.partial().extend({
notificationId: z.string().min(1),
telegramId: z.string().min(1),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
export const apiTestTelegramConnection = apiCreateTelegram.pick({
botToken: true,
chatId: true,
+ messageThreadId: true,
});
export const apiCreateDiscord = notificationsSchema
@@ -188,6 +194,7 @@ export const apiCreateDiscord = notificationsSchema
name: true,
appDeploy: true,
dockerCleanup: true,
+ serverThreshold: true,
})
.extend({
webhookUrl: z.string().min(1),
@@ -198,7 +205,7 @@ export const apiCreateDiscord = notificationsSchema
export const apiUpdateDiscord = apiCreateDiscord.partial().extend({
notificationId: z.string().min(1),
discordId: z.string().min(1),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
export const apiTestDiscordConnection = apiCreateDiscord
@@ -217,6 +224,7 @@ export const apiCreateEmail = notificationsSchema
name: true,
appDeploy: true,
dockerCleanup: true,
+ serverThreshold: true,
})
.extend({
smtpServer: z.string().min(1),
@@ -231,7 +239,7 @@ export const apiCreateEmail = notificationsSchema
export const apiUpdateEmail = apiCreateEmail.partial().extend({
notificationId: z.string().min(1),
emailId: z.string().min(1),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
export const apiTestEmailConnection = apiCreateEmail.pick({
@@ -263,7 +271,7 @@ export const apiCreateGotify = notificationsSchema
export const apiUpdateGotify = apiCreateGotify.partial().extend({
notificationId: z.string().min(1),
gotifyId: z.string().min(1),
- adminId: z.string().optional(),
+ organizationId: z.string().optional(),
});
export const apiTestGotifyConnection = apiCreateGotify
diff --git a/packages/server/src/db/schema/project.ts b/packages/server/src/db/schema/project.ts
index 7ed140d6f..deeba4aca 100644
--- a/packages/server/src/db/schema/project.ts
+++ b/packages/server/src/db/schema/project.ts
@@ -1,10 +1,9 @@
import { relations } from "drizzle-orm";
-
import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
import { applications } from "./application";
import { compose } from "./compose";
import { mariadb } from "./mariadb";
@@ -23,9 +22,10 @@ export const projects = pgTable("project", {
createdAt: text("createdAt")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- adminId: text("adminId")
+
+ organizationId: text("organizationId")
.notNull()
- .references(() => admins.adminId, { onDelete: "cascade" }),
+ .references(() => organization.id, { onDelete: "cascade" }),
env: text("env").notNull().default(""),
});
@@ -37,9 +37,9 @@ export const projectRelations = relations(projects, ({ many, one }) => ({
mongo: many(mongo),
redis: many(redis),
compose: many(compose),
- admin: one(admins, {
- fields: [projects.adminId],
- references: [admins.adminId],
+ organization: one(organization, {
+ fields: [projects.organizationId],
+ references: [organization.id],
}),
}));
diff --git a/packages/server/src/db/schema/registry.ts b/packages/server/src/db/schema/registry.ts
index 20544a587..b18747095 100644
--- a/packages/server/src/db/schema/registry.ts
+++ b/packages/server/src/db/schema/registry.ts
@@ -3,7 +3,7 @@ import { pgEnum, pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
+import { organization } from "./account";
import { applications } from "./application";
/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
@@ -27,16 +27,12 @@ export const registry = pgTable("registry", {
.notNull()
.$defaultFn(() => new Date().toISOString()),
registryType: registryType("selfHosted").notNull().default("cloud"),
- adminId: text("adminId")
+ organizationId: text("organizationId")
.notNull()
- .references(() => admins.adminId, { onDelete: "cascade" }),
+ .references(() => organization.id, { onDelete: "cascade" }),
});
-export const registryRelations = relations(registry, ({ one, many }) => ({
- admin: one(admins, {
- fields: [registry.adminId],
- references: [admins.adminId],
- }),
+export const registryRelations = relations(registry, ({ many }) => ({
applications: many(applications),
}));
@@ -45,7 +41,7 @@ const createSchema = createInsertSchema(registry, {
username: z.string().min(1),
password: z.string().min(1),
registryUrl: z.string(),
- adminId: z.string().min(1),
+ organizationId: z.string().min(1),
registryId: z.string().min(1),
registryType: z.enum(["cloud"]),
imagePrefix: z.string().nullable().optional(),
diff --git a/packages/server/src/db/schema/schema.dbml b/packages/server/src/db/schema/schema.dbml
new file mode 100644
index 000000000..ce2b5abca
--- /dev/null
+++ b/packages/server/src/db/schema/schema.dbml
@@ -0,0 +1,872 @@
+enum applicationStatus {
+ idle
+ running
+ done
+ error
+}
+
+enum buildType {
+ dockerfile
+ heroku_buildpacks
+ paketo_buildpacks
+ nixpacks
+ static
+}
+
+enum certificateType {
+ letsencrypt
+ none
+}
+
+enum composeType {
+ "docker-compose"
+ stack
+}
+
+enum databaseType {
+ postgres
+ mariadb
+ mysql
+ mongo
+}
+
+enum deploymentStatus {
+ running
+ done
+ error
+}
+
+enum domainType {
+ compose
+ application
+ preview
+}
+
+enum gitProviderType {
+ github
+ gitlab
+ bitbucket
+}
+
+enum mountType {
+ bind
+ volume
+ file
+}
+
+enum notificationType {
+ slack
+ telegram
+ discord
+ email
+ gotify
+}
+
+enum protocolType {
+ tcp
+ udp
+}
+
+enum RegistryType {
+ selfHosted
+ cloud
+}
+
+enum Roles {
+ admin
+ user
+}
+
+enum serverStatus {
+ active
+ inactive
+}
+
+enum serviceType {
+ application
+ postgres
+ mysql
+ mariadb
+ mongo
+ redis
+ compose
+}
+
+enum sourceType {
+ docker
+ git
+ github
+ gitlab
+ bitbucket
+ drop
+}
+
+enum sourceTypeCompose {
+ git
+ github
+ gitlab
+ bitbucket
+ raw
+}
+
+table account {
+ id text [pk, not null]
+ account_id text [not null]
+ provider_id text [not null]
+ user_id text [not null]
+ access_token text
+ refresh_token text
+ id_token text
+ access_token_expires_at timestamp
+ refresh_token_expires_at timestamp
+ scope text
+ password text
+ is2FAEnabled boolean [not null, default: false]
+ created_at timestamp [not null]
+ updated_at timestamp [not null]
+ resetPasswordToken text
+ resetPasswordExpiresAt text
+ confirmationToken text
+ confirmationExpiresAt text
+}
+
+table admin {
+}
+
+table application {
+ applicationId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ description text
+ env text
+ previewEnv text
+ previewBuildArgs text
+ previewWildcard text
+ previewPort integer [default: 3000]
+ previewHttps boolean [not null, default: false]
+ previewPath text [default: '/']
+ certificateType certificateType [not null, default: 'none']
+ previewLimit integer [default: 3]
+ isPreviewDeploymentsActive boolean [default: false]
+ buildArgs text
+ memoryReservation text
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ title text
+ enabled boolean
+ subtitle text
+ command text
+ refreshToken text
+ sourceType sourceType [not null, default: 'github']
+ repository text
+ owner text
+ branch text
+ buildPath text [default: '/']
+ autoDeploy boolean
+ gitlabProjectId integer
+ gitlabRepository text
+ gitlabOwner text
+ gitlabBranch text
+ gitlabBuildPath text [default: '/']
+ gitlabPathNamespace text
+ bitbucketRepository text
+ bitbucketOwner text
+ bitbucketBranch text
+ bitbucketBuildPath text [default: '/']
+ username text
+ password text
+ dockerImage text
+ registryUrl text
+ customGitUrl text
+ customGitBranch text
+ customGitBuildPath text
+ customGitSSHKeyId text
+ dockerfile text
+ dockerContextPath text
+ dockerBuildStage text
+ dropBuildPath text
+ healthCheckSwarm json
+ restartPolicySwarm json
+ placementSwarm json
+ updateConfigSwarm json
+ rollbackConfigSwarm json
+ modeSwarm json
+ labelsSwarm json
+ networkSwarm json
+ replicas integer [not null, default: 1]
+ applicationStatus applicationStatus [not null, default: 'idle']
+ buildType buildType [not null, default: 'nixpacks']
+ herokuVersion text [default: '24']
+ publishDirectory text
+ createdAt text [not null]
+ registryId text
+ projectId text [not null]
+ githubId text
+ gitlabId text
+ bitbucketId text
+ serverId text
+}
+
+table auth {
+ id text [pk, not null]
+ email text [not null, unique]
+ password text [not null]
+ rol Roles [not null]
+ image text
+ secret text
+ token text
+ is2FAEnabled boolean [not null, default: false]
+ createdAt text [not null]
+ resetPasswordToken text
+ resetPasswordExpiresAt text
+ confirmationToken text
+ confirmationExpiresAt text
+}
+
+table backup {
+ backupId text [pk, not null]
+ schedule text [not null]
+ enabled boolean
+ database text [not null]
+ prefix text [not null]
+ destinationId text [not null]
+ databaseType databaseType [not null]
+ postgresId text
+ mariadbId text
+ mysqlId text
+ mongoId text
+}
+
+table bitbucket {
+ bitbucketId text [pk, not null]
+ bitbucketUsername text
+ appPassword text
+ bitbucketWorkspaceName text
+ gitProviderId text [not null]
+}
+
+table certificate {
+ certificateId text [pk, not null]
+ name text [not null]
+ certificateData text [not null]
+ privateKey text [not null]
+ certificatePath text [not null, unique]
+ autoRenew boolean
+ userId text
+ serverId text
+}
+
+table compose {
+ composeId text [pk, not null]
+ name text [not null]
+ appName text [not null]
+ description text
+ env text
+ composeFile text [not null, default: '']
+ refreshToken text
+ sourceType sourceTypeCompose [not null, default: 'github']
+ composeType composeType [not null, default: 'docker-compose']
+ repository text
+ owner text
+ branch text
+ autoDeploy boolean
+ gitlabProjectId integer
+ gitlabRepository text
+ gitlabOwner text
+ gitlabBranch text
+ gitlabPathNamespace text
+ bitbucketRepository text
+ bitbucketOwner text
+ bitbucketBranch text
+ customGitUrl text
+ customGitBranch text
+ customGitSSHKeyId text
+ command text [not null, default: '']
+ composePath text [not null, default: './docker-compose.yml']
+ suffix text [not null, default: '']
+ randomize boolean [not null, default: false]
+ isolatedDeployment boolean [not null, default: false]
+ composeStatus applicationStatus [not null, default: 'idle']
+ projectId text [not null]
+ createdAt text [not null]
+ githubId text
+ gitlabId text
+ bitbucketId text
+ serverId text
+}
+
+table deployment {
+ deploymentId text [pk, not null]
+ title text [not null]
+ description text
+ status deploymentStatus [default: 'running']
+ logPath text [not null]
+ applicationId text
+ composeId text
+ serverId text
+ isPreviewDeployment boolean [default: false]
+ previewDeploymentId text
+ createdAt text [not null]
+ errorMessage text
+}
+
+table destination {
+ destinationId text [pk, not null]
+ name text [not null]
+ provider text
+ accessKey text [not null]
+ secretAccessKey text [not null]
+ bucket text [not null]
+ region text [not null]
+ endpoint text [not null]
+ userId text [not null]
+}
+
+table discord {
+ discordId text [pk, not null]
+ webhookUrl text [not null]
+ decoration boolean
+}
+
+table domain {
+ domainId text [pk, not null]
+ host text [not null]
+ https boolean [not null, default: false]
+ port integer [default: 3000]
+ path text [default: '/']
+ serviceName text
+ domainType domainType [default: 'application']
+ uniqueConfigKey serial [not null, increment]
+ createdAt text [not null]
+ composeId text
+ applicationId text
+ previewDeploymentId text
+ certificateType certificateType [not null, default: 'none']
+}
+
+table email {
+ emailId text [pk, not null]
+ smtpServer text [not null]
+ smtpPort integer [not null]
+ username text [not null]
+ password text [not null]
+ fromAddress text [not null]
+ toAddress text[] [not null]
+}
+
+table git_provider {
+ gitProviderId text [pk, not null]
+ name text [not null]
+ providerType gitProviderType [not null, default: 'github']
+ createdAt text [not null]
+ userId text
+}
+
+table github {
+ githubId text [pk, not null]
+ githubAppName text
+ githubAppId integer
+ githubClientId text
+ githubClientSecret text
+ githubInstallationId text
+ githubPrivateKey text
+ githubWebhookSecret text
+ gitProviderId text [not null]
+}
+
+table gitlab {
+ gitlabId text [pk, not null]
+ gitlabUrl text [not null, default: 'https://gitlab.com']
+ application_id text
+ redirect_uri text
+ secret text
+ access_token text
+ refresh_token text
+ group_name text
+ expires_at integer
+ gitProviderId text [not null]
+}
+
+table gotify {
+ gotifyId text [pk, not null]
+ serverUrl text [not null]
+ appToken text [not null]
+ priority integer [not null, default: 5]
+ decoration boolean
+}
+
+table invitation {
+ id text [pk, not null]
+ organization_id text [not null]
+ email text [not null]
+ role text
+ status text [not null]
+ expires_at timestamp [not null]
+ inviter_id text [not null]
+}
+
+table mariadb {
+ mariadbId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ description text
+ databaseName text [not null]
+ databaseUser text [not null]
+ databasePassword text [not null]
+ rootPassword text [not null]
+ dockerImage text [not null]
+ command text
+ env text
+ memoryReservation text
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ externalPort integer
+ applicationStatus applicationStatus [not null, default: 'idle']
+ createdAt text [not null]
+ projectId text [not null]
+ serverId text
+}
+
+table member {
+ id text [pk, not null]
+ organization_id text [not null]
+ user_id text [not null]
+ role text [not null]
+ created_at timestamp [not null]
+}
+
+table mongo {
+ mongoId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ description text
+ databaseUser text [not null]
+ databasePassword text [not null]
+ dockerImage text [not null]
+ command text
+ env text
+ memoryReservation text
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ externalPort integer
+ applicationStatus applicationStatus [not null, default: 'idle']
+ createdAt text [not null]
+ projectId text [not null]
+ serverId text
+ replicaSets boolean [default: false]
+}
+
+table mount {
+ mountId text [pk, not null]
+ type mountType [not null]
+ hostPath text
+ volumeName text
+ filePath text
+ content text
+ serviceType serviceType [not null, default: 'application']
+ mountPath text [not null]
+ applicationId text
+ postgresId text
+ mariadbId text
+ mongoId text
+ mysqlId text
+ redisId text
+ composeId text
+}
+
+table mysql {
+ mysqlId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ description text
+ databaseName text [not null]
+ databaseUser text [not null]
+ databasePassword text [not null]
+ rootPassword text [not null]
+ dockerImage text [not null]
+ command text
+ env text
+ memoryReservation text
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ externalPort integer
+ applicationStatus applicationStatus [not null, default: 'idle']
+ createdAt text [not null]
+ projectId text [not null]
+ serverId text
+}
+
+table notification {
+ notificationId text [pk, not null]
+ name text [not null]
+ appDeploy boolean [not null, default: false]
+ appBuildError boolean [not null, default: false]
+ databaseBackup boolean [not null, default: false]
+ dokployRestart boolean [not null, default: false]
+ dockerCleanup boolean [not null, default: false]
+ serverThreshold boolean [not null, default: false]
+ notificationType notificationType [not null]
+ createdAt text [not null]
+ slackId text
+ telegramId text
+ discordId text
+ emailId text
+ gotifyId text
+ userId text
+}
+
+table organization {
+ id text [pk, not null]
+ name text [not null]
+ slug text [unique]
+ logo text
+ created_at timestamp [not null]
+ metadata text
+ owner_id text [not null]
+}
+
+table port {
+ portId text [pk, not null]
+ publishedPort integer [not null]
+ targetPort integer [not null]
+ protocol protocolType [not null]
+ applicationId text [not null]
+}
+
+table postgres {
+ postgresId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ databaseName text [not null]
+ databaseUser text [not null]
+ databasePassword text [not null]
+ description text
+ dockerImage text [not null]
+ command text
+ env text
+ memoryReservation text
+ externalPort integer
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ applicationStatus applicationStatus [not null, default: 'idle']
+ createdAt text [not null]
+ projectId text [not null]
+ serverId text
+}
+
+table preview_deployments {
+ previewDeploymentId text [pk, not null]
+ branch text [not null]
+ pullRequestId text [not null]
+ pullRequestNumber text [not null]
+ pullRequestURL text [not null]
+ pullRequestTitle text [not null]
+ pullRequestCommentId text [not null]
+ previewStatus applicationStatus [not null, default: 'idle']
+ appName text [not null, unique]
+ applicationId text [not null]
+ domainId text
+ createdAt text [not null]
+ expiresAt text
+}
+
+table project {
+ projectId text [pk, not null]
+ name text [not null]
+ description text
+ createdAt text [not null]
+ userId text [not null]
+ env text [not null, default: '']
+}
+
+table redirect {
+ redirectId text [pk, not null]
+ regex text [not null]
+ replacement text [not null]
+ permanent boolean [not null, default: false]
+ uniqueConfigKey serial [not null, increment]
+ createdAt text [not null]
+ applicationId text [not null]
+}
+
+table redis {
+ redisId text [pk, not null]
+ name text [not null]
+ appName text [not null, unique]
+ description text
+ password text [not null]
+ dockerImage text [not null]
+ command text
+ env text
+ memoryReservation text
+ memoryLimit text
+ cpuReservation text
+ cpuLimit text
+ externalPort integer
+ createdAt text [not null]
+ applicationStatus applicationStatus [not null, default: 'idle']
+ projectId text [not null]
+ serverId text
+}
+
+table registry {
+ registryId text [pk, not null]
+ registryName text [not null]
+ imagePrefix text
+ username text [not null]
+ password text [not null]
+ registryUrl text [not null, default: '']
+ createdAt text [not null]
+ selfHosted RegistryType [not null, default: 'cloud']
+ userId text [not null]
+}
+
+table security {
+ securityId text [pk, not null]
+ username text [not null]
+ password text [not null]
+ createdAt text [not null]
+ applicationId text [not null]
+
+ indexes {
+ (username, applicationId) [name: 'security_username_applicationId_unique', unique]
+ }
+}
+
+table server {
+ serverId text [pk, not null]
+ name text [not null]
+ description text
+ ipAddress text [not null]
+ port integer [not null]
+ username text [not null, default: 'root']
+ appName text [not null]
+ enableDockerCleanup boolean [not null, default: false]
+ createdAt text [not null]
+ userId text [not null]
+ serverStatus serverStatus [not null, default: 'active']
+ command text [not null, default: '']
+ sshKeyId text
+ metricsConfig jsonb [not null, default: `{"server":{"type":"Remote","refreshRate":60,"port":4500,"token":"","urlCallback":"","cronJob":"","retentionDays":2,"thresholds":{"cpu":0,"memory":0}},"containers":{"refreshRate":60,"services":{"include":[],"exclude":[]}}}`]
+}
+
+table session {
+ id text [pk, not null]
+ expires_at timestamp [not null]
+ token text [not null, unique]
+ created_at timestamp [not null]
+ updated_at timestamp [not null]
+ ip_address text
+ user_agent text
+ user_id text [not null]
+ impersonated_by text
+ active_organization_id text
+}
+
+table slack {
+ slackId text [pk, not null]
+ webhookUrl text [not null]
+ channel text
+}
+
+table "ssh-key" {
+ sshKeyId text [pk, not null]
+ privateKey text [not null, default: '']
+ publicKey text [not null]
+ name text [not null]
+ description text
+ createdAt text [not null]
+ lastUsedAt text
+ userId text
+}
+
+table telegram {
+ telegramId text [pk, not null]
+ botToken text [not null]
+ chatId text [not null]
+}
+
+table user {
+ id text [pk, not null]
+ name text [not null, default: '']
+ token text [not null]
+ isRegistered boolean [not null, default: false]
+ expirationDate text [not null]
+ createdAt text [not null]
+ canCreateProjects boolean [not null, default: false]
+ canAccessToSSHKeys boolean [not null, default: false]
+ canCreateServices boolean [not null, default: false]
+ canDeleteProjects boolean [not null, default: false]
+ canDeleteServices boolean [not null, default: false]
+ canAccessToDocker boolean [not null, default: false]
+ canAccessToAPI boolean [not null, default: false]
+ canAccessToGitProviders boolean [not null, default: false]
+ canAccessToTraefikFiles boolean [not null, default: false]
+ accesedProjects text[] [not null, default: `ARRAY[]::text[]`]
+ accesedServices text[] [not null, default: `ARRAY[]::text[]`]
+ email text [not null, unique]
+ email_verified boolean [not null]
+ image text
+ role text
+ banned boolean
+ ban_reason text
+ ban_expires timestamp
+ updated_at timestamp [not null]
+ serverIp text
+ certificateType certificateType [not null, default: 'none']
+ host text
+ letsEncryptEmail text
+ sshPrivateKey text
+ enableDockerCleanup boolean [not null, default: false]
+ enableLogRotation boolean [not null, default: false]
+ enablePaidFeatures boolean [not null, default: false]
+ metricsConfig jsonb [not null, default: `{"server":{"type":"Dokploy","refreshRate":60,"port":4500,"token":"","retentionDays":2,"cronJob":"","urlCallback":"","thresholds":{"cpu":0,"memory":0}},"containers":{"refreshRate":60,"services":{"include":[],"exclude":[]}}}`]
+ cleanupCacheApplications boolean [not null, default: false]
+ cleanupCacheOnPreviews boolean [not null, default: false]
+ cleanupCacheOnCompose boolean [not null, default: false]
+ stripeCustomerId text
+ stripeSubscriptionId text
+ serversQuantity integer [not null, default: 0]
+}
+
+table verification {
+ id text [pk, not null]
+ identifier text [not null]
+ value text [not null]
+ expires_at timestamp [not null]
+ created_at timestamp
+ updated_at timestamp
+}
+
+ref: mount.applicationId > application.applicationId
+
+ref: mount.postgresId > postgres.postgresId
+
+ref: mount.mariadbId > mariadb.mariadbId
+
+ref: mount.mongoId > mongo.mongoId
+
+ref: mount.mysqlId > mysql.mysqlId
+
+ref: mount.redisId > redis.redisId
+
+ref: mount.composeId > compose.composeId
+
+ref: application.projectId > project.projectId
+
+ref: application.customGitSSHKeyId > "ssh-key".sshKeyId
+
+ref: application.registryId > registry.registryId
+
+ref: application.githubId - github.githubId
+
+ref: application.gitlabId - gitlab.gitlabId
+
+ref: application.bitbucketId - bitbucket.bitbucketId
+
+ref: application.serverId > server.serverId
+
+ref: backup.destinationId > destination.destinationId
+
+ref: backup.postgresId > postgres.postgresId
+
+ref: backup.mariadbId > mariadb.mariadbId
+
+ref: backup.mysqlId > mysql.mysqlId
+
+ref: backup.mongoId > mongo.mongoId
+
+ref: git_provider.gitProviderId - bitbucket.gitProviderId
+
+ref: certificate.serverId > server.serverId
+
+ref: certificate.userId - user.id
+
+ref: compose.projectId > project.projectId
+
+ref: compose.customGitSSHKeyId > "ssh-key".sshKeyId
+
+ref: compose.githubId - github.githubId
+
+ref: compose.gitlabId - gitlab.gitlabId
+
+ref: compose.bitbucketId - bitbucket.bitbucketId
+
+ref: compose.serverId > server.serverId
+
+ref: deployment.applicationId > application.applicationId
+
+ref: deployment.composeId > compose.composeId
+
+ref: deployment.serverId > server.serverId
+
+ref: deployment.previewDeploymentId > preview_deployments.previewDeploymentId
+
+ref: destination.userId - user.id
+
+ref: domain.applicationId > application.applicationId
+
+ref: domain.composeId > compose.composeId
+
+ref: preview_deployments.domainId - domain.domainId
+
+ref: github.gitProviderId - git_provider.gitProviderId
+
+ref: gitlab.gitProviderId - git_provider.gitProviderId
+
+ref: git_provider.userId - user.id
+
+ref: mariadb.projectId > project.projectId
+
+ref: mariadb.serverId > server.serverId
+
+ref: mongo.projectId > project.projectId
+
+ref: mongo.serverId > server.serverId
+
+ref: mysql.projectId > project.projectId
+
+ref: mysql.serverId > server.serverId
+
+ref: notification.slackId - slack.slackId
+
+ref: notification.telegramId - telegram.telegramId
+
+ref: notification.discordId - discord.discordId
+
+ref: notification.emailId - email.emailId
+
+ref: notification.gotifyId - gotify.gotifyId
+
+ref: notification.userId - user.id
+
+ref: port.applicationId > application.applicationId
+
+ref: postgres.projectId > project.projectId
+
+ref: postgres.serverId > server.serverId
+
+ref: preview_deployments.applicationId > application.applicationId
+
+ref: project.userId - user.id
+
+ref: redirect.applicationId > application.applicationId
+
+ref: redis.projectId > project.projectId
+
+ref: redis.serverId > server.serverId
+
+ref: registry.userId - user.id
+
+ref: security.applicationId > application.applicationId
+
+ref: server.userId - user.id
+
+ref: server.sshKeyId > "ssh-key".sshKeyId
+
+ref: "ssh-key".userId - user.id
\ No newline at end of file
diff --git a/packages/server/src/db/schema/server.ts b/packages/server/src/db/schema/server.ts
index a9c8da6ef..26bb46326 100644
--- a/packages/server/src/db/schema/server.ts
+++ b/packages/server/src/db/schema/server.ts
@@ -1,10 +1,16 @@
import { relations } from "drizzle-orm";
-import { boolean, integer, pgEnum, pgTable, text } from "drizzle-orm/pg-core";
+import {
+ boolean,
+ integer,
+ jsonb,
+ pgEnum,
+ pgTable,
+ text,
+} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-
-import { admins } from "./admin";
+import { organization } from "./account";
import { applications } from "./application";
import { certificates } from "./certificate";
import { compose } from "./compose";
@@ -33,24 +39,64 @@ export const server = pgTable("server", {
.notNull()
.$defaultFn(() => generateAppName("server")),
enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
- createdAt: text("createdAt")
+ createdAt: text("createdAt").notNull(),
+ organizationId: text("organizationId")
.notNull()
- .$defaultFn(() => new Date().toISOString()),
- adminId: text("adminId")
- .notNull()
- .references(() => admins.adminId, { onDelete: "cascade" }),
+ .references(() => organization.id, { onDelete: "cascade" }),
serverStatus: serverStatus("serverStatus").notNull().default("active"),
command: text("command").notNull().default(""),
sshKeyId: text("sshKeyId").references(() => sshKeys.sshKeyId, {
onDelete: "set null",
}),
+ metricsConfig: jsonb("metricsConfig")
+ .$type<{
+ server: {
+ type: "Dokploy" | "Remote";
+ refreshRate: number;
+ port: number;
+ token: string;
+ urlCallback: string;
+ retentionDays: number;
+ cronJob: string;
+ thresholds: {
+ cpu: number;
+ memory: number;
+ };
+ };
+ containers: {
+ refreshRate: number;
+ services: {
+ include: string[];
+ exclude: string[];
+ };
+ };
+ }>()
+ .notNull()
+ .default({
+ server: {
+ type: "Remote",
+ refreshRate: 60,
+ port: 4500,
+ token: "",
+ urlCallback: "",
+ cronJob: "",
+ retentionDays: 2,
+ thresholds: {
+ cpu: 0,
+ memory: 0,
+ },
+ },
+ containers: {
+ refreshRate: 60,
+ services: {
+ include: [],
+ exclude: [],
+ },
+ },
+ }),
});
export const serverRelations = relations(server, ({ one, many }) => ({
- admin: one(admins, {
- fields: [server.adminId],
- references: [admins.adminId],
- }),
deployments: many(deployments),
sshKey: one(sshKeys, {
fields: [server.sshKeyId],
@@ -64,6 +110,10 @@ export const serverRelations = relations(server, ({ one, many }) => ({
mysql: many(mysql),
postgres: many(postgres),
certificates: many(certificates),
+ organization: one(organization, {
+ fields: [server.organizationId],
+ references: [organization.id],
+ }),
}));
const createSchema = createInsertSchema(server, {
@@ -109,3 +159,34 @@ export const apiUpdateServer = createSchema
.extend({
command: z.string().optional(),
});
+
+export const apiUpdateServerMonitoring = createSchema
+ .pick({
+ serverId: true,
+ })
+ .required()
+ .extend({
+ metricsConfig: z
+ .object({
+ server: z.object({
+ refreshRate: z.number().min(2),
+ port: z.number().min(1),
+ token: z.string(),
+ urlCallback: z.string().url(),
+ retentionDays: z.number().min(1),
+ cronJob: z.string().min(1),
+ thresholds: z.object({
+ cpu: z.number().min(0),
+ memory: z.number().min(0),
+ }),
+ }),
+ containers: z.object({
+ refreshRate: z.number().min(2),
+ services: z.object({
+ include: z.array(z.string()).optional(),
+ exclude: z.array(z.string()).optional(),
+ }),
+ }),
+ })
+ .required(),
+ });
diff --git a/packages/server/src/db/schema/session.ts b/packages/server/src/db/schema/session.ts
index 1b6d8cc17..f7c12dae0 100644
--- a/packages/server/src/db/schema/session.ts
+++ b/packages/server/src/db/schema/session.ts
@@ -1,13 +1,18 @@
import { pgTable, text, timestamp } from "drizzle-orm/pg-core";
-import { auth } from "./auth";
+import { users_temp } from "./user";
-export const sessionTable = pgTable("session", {
+// OLD TABLE
+export const session = pgTable("session_temp", {
id: text("id").primaryKey(),
+ expiresAt: timestamp("expires_at").notNull(),
+ token: text("token").notNull().unique(),
+ createdAt: timestamp("created_at").notNull(),
+ updatedAt: timestamp("updated_at").notNull(),
+ ipAddress: text("ip_address"),
+ userAgent: text("user_agent"),
userId: text("user_id")
.notNull()
- .references(() => auth.id, { onDelete: "cascade" }),
- expiresAt: timestamp("expires_at", {
- withTimezone: true,
- mode: "date",
- }).notNull(),
+ .references(() => users_temp.id, { onDelete: "cascade" }),
+ impersonatedBy: text("impersonated_by"),
+ activeOrganizationId: text("active_organization_id"),
});
diff --git a/packages/server/src/db/schema/source.ts b/packages/server/src/db/schema/source.ts
deleted file mode 100644
index 6618ced77..000000000
--- a/packages/server/src/db/schema/source.ts
+++ /dev/null
@@ -1,27 +0,0 @@
-import { pgTable, text } from "drizzle-orm/pg-core";
-import { createInsertSchema } from "drizzle-zod";
-import { nanoid } from "nanoid";
-import { z } from "zod";
-
-export const source = pgTable("project", {
- projectId: text("projectId")
- .notNull()
- .primaryKey()
- .$defaultFn(() => nanoid()),
- name: text("name").notNull(),
- description: text("description"),
- createdAt: text("createdAt")
- .notNull()
- .$defaultFn(() => new Date().toISOString()),
-});
-
-const createSchema = createInsertSchema(source, {
- name: z.string().min(1),
- description: z.string(),
- projectId: z.string(),
-});
-
-export const apiCreate = createSchema.pick({
- name: true,
- description: true,
-});
diff --git a/packages/server/src/db/schema/ssh-key.ts b/packages/server/src/db/schema/ssh-key.ts
index e4842851b..8a66d6d9d 100644
--- a/packages/server/src/db/schema/ssh-key.ts
+++ b/packages/server/src/db/schema/ssh-key.ts
@@ -3,7 +3,7 @@ import { pgTable, text } from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { sshKeyCreate, sshKeyType } from "../validations";
-import { admins } from "./admin";
+import { organization } from "./account";
import { applications } from "./application";
import { compose } from "./compose";
import { server } from "./server";
@@ -21,18 +21,18 @@ export const sshKeys = pgTable("ssh-key", {
.notNull()
.$defaultFn(() => new Date().toISOString()),
lastUsedAt: text("lastUsedAt"),
- adminId: text("adminId").references(() => admins.adminId, {
- onDelete: "cascade",
- }),
+ organizationId: text("organizationId")
+ .notNull()
+ .references(() => organization.id, { onDelete: "cascade" }),
});
export const sshKeysRelations = relations(sshKeys, ({ many, one }) => ({
applications: many(applications),
compose: many(compose),
servers: many(server),
- admin: one(admins, {
- fields: [sshKeys.adminId],
- references: [admins.adminId],
+ organization: one(organization, {
+ fields: [sshKeys.organizationId],
+ references: [organization.id],
}),
}));
@@ -48,7 +48,7 @@ export const apiCreateSshKey = createSchema
description: true,
privateKey: true,
publicKey: true,
- adminId: true,
+ organizationId: true,
})
.merge(sshKeyCreate.pick({ privateKey: true }));
diff --git a/packages/server/src/db/schema/user.ts b/packages/server/src/db/schema/user.ts
index 735898f9a..9307127a1 100644
--- a/packages/server/src/db/schema/user.ts
+++ b/packages/server/src/db/schema/user.ts
@@ -1,10 +1,18 @@
-import { relations, sql } from "drizzle-orm";
-import { boolean, pgTable, text, timestamp } from "drizzle-orm/pg-core";
+import { relations } from "drizzle-orm";
+import {
+ boolean,
+ integer,
+ jsonb,
+ pgTable,
+ text,
+ timestamp,
+} from "drizzle-orm/pg-core";
import { createInsertSchema } from "drizzle-zod";
import { nanoid } from "nanoid";
import { z } from "zod";
-import { admins } from "./admin";
-import { auth } from "./auth";
+import { account, organization, apikey } from "./account";
+import { projects } from "./project";
+import { certificateType } from "./shared";
/**
* This is an example of how to use the multi-project schema feature of Drizzle ORM. Use the same
* database instance for multiple projects.
@@ -12,75 +20,115 @@ import { auth } from "./auth";
* @see https://orm.drizzle.team/docs/goodies#multi-project-schema
*/
-export const users = pgTable("user", {
- userId: text("userId")
+// OLD TABLE
+
+// TEMP
+export const users_temp = pgTable("user_temp", {
+ id: text("id")
.notNull()
.primaryKey()
.$defaultFn(() => nanoid()),
-
- token: text("token").notNull(),
+ name: text("name").notNull().default(""),
isRegistered: boolean("isRegistered").notNull().default(false),
- expirationDate: timestamp("expirationDate", {
- precision: 3,
- mode: "string",
- }).notNull(),
- createdAt: text("createdAt")
+ expirationDate: text("expirationDate")
.notNull()
.$defaultFn(() => new Date().toISOString()),
- canCreateProjects: boolean("canCreateProjects").notNull().default(false),
- canAccessToSSHKeys: boolean("canAccessToSSHKeys").notNull().default(false),
- canCreateServices: boolean("canCreateServices").notNull().default(false),
- canDeleteProjects: boolean("canDeleteProjects").notNull().default(false),
- canDeleteServices: boolean("canDeleteServices").notNull().default(false),
- canAccessToDocker: boolean("canAccessToDocker").notNull().default(false),
- canAccessToAPI: boolean("canAccessToAPI").notNull().default(false),
- canAccessToGitProviders: boolean("canAccessToGitProviders")
+ createdAt2: text("createdAt")
+ .notNull()
+ .$defaultFn(() => new Date().toISOString()),
+ createdAt: timestamp("created_at").defaultNow(),
+ // Auth
+ twoFactorEnabled: boolean("two_factor_enabled"),
+ email: text("email").notNull().unique(),
+ emailVerified: boolean("email_verified").notNull(),
+ image: text("image"),
+ banned: boolean("banned"),
+ banReason: text("ban_reason"),
+ banExpires: timestamp("ban_expires"),
+ updatedAt: timestamp("updated_at").notNull(),
+ // Admin
+ serverIp: text("serverIp"),
+ certificateType: certificateType("certificateType").notNull().default("none"),
+ host: text("host"),
+ letsEncryptEmail: text("letsEncryptEmail"),
+ sshPrivateKey: text("sshPrivateKey"),
+ enableDockerCleanup: boolean("enableDockerCleanup").notNull().default(false),
+ enableLogRotation: boolean("enableLogRotation").notNull().default(false),
+ // Metrics
+ enablePaidFeatures: boolean("enablePaidFeatures").notNull().default(false),
+ metricsConfig: jsonb("metricsConfig")
+ .$type<{
+ server: {
+ type: "Dokploy" | "Remote";
+ refreshRate: number;
+ port: number;
+ token: string;
+ urlCallback: string;
+ retentionDays: number;
+ cronJob: string;
+ thresholds: {
+ cpu: number;
+ memory: number;
+ };
+ };
+ containers: {
+ refreshRate: number;
+ services: {
+ include: string[];
+ exclude: string[];
+ };
+ };
+ }>()
+ .notNull()
+ .default({
+ server: {
+ type: "Dokploy",
+ refreshRate: 60,
+ port: 4500,
+ token: "",
+ retentionDays: 2,
+ cronJob: "",
+ urlCallback: "",
+ thresholds: {
+ cpu: 0,
+ memory: 0,
+ },
+ },
+ containers: {
+ refreshRate: 60,
+ services: {
+ include: [],
+ exclude: [],
+ },
+ },
+ }),
+ cleanupCacheApplications: boolean("cleanupCacheApplications")
.notNull()
.default(false),
- canAccessToTraefikFiles: boolean("canAccessToTraefikFiles")
+ cleanupCacheOnPreviews: boolean("cleanupCacheOnPreviews")
.notNull()
.default(false),
- accessedProjects: text("accesedProjects")
- .array()
+ cleanupCacheOnCompose: boolean("cleanupCacheOnCompose")
.notNull()
- .default(sql`ARRAY[]::text[]`),
- accessedServices: text("accesedServices")
- .array()
- .notNull()
- .default(sql`ARRAY[]::text[]`),
- adminId: text("adminId")
- .notNull()
- .references(() => admins.adminId, { onDelete: "cascade" }),
- authId: text("authId")
- .notNull()
- .references(() => auth.id, { onDelete: "cascade" }),
+ .default(false),
+ stripeCustomerId: text("stripeCustomerId"),
+ stripeSubscriptionId: text("stripeSubscriptionId"),
+ serversQuantity: integer("serversQuantity").notNull().default(0),
});
-export const usersRelations = relations(users, ({ one }) => ({
- auth: one(auth, {
- fields: [users.authId],
- references: [auth.id],
- }),
- admin: one(admins, {
- fields: [users.adminId],
- references: [admins.adminId],
+export const usersRelations = relations(users_temp, ({ one, many }) => ({
+ account: one(account, {
+ fields: [users_temp.id],
+ references: [account.userId],
}),
+ organizations: many(organization),
+ projects: many(projects),
+ apiKeys: many(apikey),
}));
-const createSchema = createInsertSchema(users, {
- userId: z.string().min(1),
- authId: z.string().min(1),
- token: z.string().min(1),
+const createSchema = createInsertSchema(users_temp, {
+ id: z.string().min(1),
isRegistered: z.boolean().optional(),
- adminId: z.string(),
- accessedProjects: z.array(z.string()).optional(),
- accessedServices: z.array(z.string()).optional(),
- canCreateProjects: z.boolean().optional(),
- canCreateServices: z.boolean().optional(),
- canDeleteProjects: z.boolean().optional(),
- canDeleteServices: z.boolean().optional(),
- canAccessToDocker: z.boolean().optional(),
- canAccessToTraefikFiles: z.boolean().optional(),
});
export const apiCreateUserInvitation = createSchema.pick({}).extend({
@@ -89,41 +137,172 @@ export const apiCreateUserInvitation = createSchema.pick({}).extend({
export const apiRemoveUser = createSchema
.pick({
- authId: true,
+ id: true,
})
.required();
export const apiFindOneToken = createSchema
- .pick({
- token: true,
- })
- .required();
+ .pick({})
+ .required()
+ .extend({
+ token: z.string().min(1),
+ });
export const apiAssignPermissions = createSchema
.pick({
- userId: true,
- canCreateProjects: true,
- canCreateServices: true,
- canDeleteProjects: true,
- canDeleteServices: true,
- accessedProjects: true,
- accessedServices: true,
- canAccessToTraefikFiles: true,
- canAccessToDocker: true,
- canAccessToAPI: true,
- canAccessToSSHKeys: true,
- canAccessToGitProviders: true,
+ id: true,
+ // canCreateProjects: true,
+ // canCreateServices: true,
+ // canDeleteProjects: true,
+ // canDeleteServices: true,
+ // accessedProjects: true,
+ // accessedServices: true,
+ // canAccessToTraefikFiles: true,
+ // canAccessToDocker: true,
+ // canAccessToAPI: true,
+ // canAccessToSSHKeys: true,
+ // canAccessToGitProviders: true,
+ })
+ .extend({
+ accessedProjects: z.array(z.string()).optional(),
+ accessedServices: z.array(z.string()).optional(),
+ canCreateProjects: z.boolean().optional(),
+ canCreateServices: z.boolean().optional(),
+ canDeleteProjects: z.boolean().optional(),
+ canDeleteServices: z.boolean().optional(),
+ canAccessToDocker: z.boolean().optional(),
+ canAccessToTraefikFiles: z.boolean().optional(),
+ canAccessToAPI: z.boolean().optional(),
+ canAccessToSSHKeys: z.boolean().optional(),
+ canAccessToGitProviders: z.boolean().optional(),
})
.required();
export const apiFindOneUser = createSchema
.pick({
- userId: true,
+ id: true,
})
.required();
export const apiFindOneUserByAuth = createSchema
.pick({
- authId: true,
+ // authId: true,
})
.required();
+export const apiSaveSSHKey = createSchema
+ .pick({
+ sshPrivateKey: true,
+ })
+ .required();
+
+export const apiAssignDomain = createSchema
+ .pick({
+ host: true,
+ certificateType: true,
+ letsEncryptEmail: true,
+ })
+ .required()
+ .partial({
+ letsEncryptEmail: true,
+ });
+
+export const apiUpdateDockerCleanup = createSchema
+ .pick({
+ enableDockerCleanup: true,
+ })
+ .required()
+ .extend({
+ serverId: z.string().optional(),
+ });
+
+export const apiTraefikConfig = z.object({
+ traefikConfig: z.string().min(1),
+});
+
+export const apiModifyTraefikConfig = z.object({
+ path: z.string().min(1),
+ traefikConfig: z.string().min(1),
+ serverId: z.string().optional(),
+});
+export const apiReadTraefikConfig = z.object({
+ path: z.string().min(1),
+ serverId: z.string().optional(),
+});
+
+export const apiEnableDashboard = z.object({
+ enableDashboard: z.boolean().optional(),
+ serverId: z.string().optional(),
+});
+
+export const apiServerSchema = z
+ .object({
+ serverId: z.string().optional(),
+ })
+ .optional();
+
+export const apiReadStatsLogs = z.object({
+ page: z
+ .object({
+ pageIndex: z.number(),
+ pageSize: z.number(),
+ })
+ .optional(),
+ status: z.string().array().optional(),
+ search: z.string().optional(),
+ sort: z.object({ id: z.string(), desc: z.boolean() }).optional(),
+});
+
+export const apiUpdateWebServerMonitoring = z.object({
+ metricsConfig: z
+ .object({
+ server: z.object({
+ refreshRate: z.number().min(2),
+ port: z.number().min(1),
+ token: z.string(),
+ urlCallback: z.string().url(),
+ retentionDays: z.number().min(1),
+ cronJob: z.string().min(1),
+ thresholds: z.object({
+ cpu: z.number().min(0),
+ memory: z.number().min(0),
+ }),
+ }),
+ containers: z.object({
+ refreshRate: z.number().min(2),
+ services: z.object({
+ include: z.array(z.string()).optional(),
+ exclude: z.array(z.string()).optional(),
+ }),
+ }),
+ })
+ .required(),
+});
+
+export const apiUpdateUser = createSchema.partial().extend({
+ password: z.string().optional(),
+ currentPassword: z.string().optional(),
+ metricsConfig: z
+ .object({
+ server: z.object({
+ type: z.enum(["Dokploy", "Remote"]),
+ refreshRate: z.number(),
+ port: z.number(),
+ token: z.string(),
+ urlCallback: z.string(),
+ retentionDays: z.number(),
+ cronJob: z.string(),
+ thresholds: z.object({
+ cpu: z.number(),
+ memory: z.number(),
+ }),
+ }),
+ containers: z.object({
+ refreshRate: z.number(),
+ services: z.object({
+ include: z.array(z.string()),
+ exclude: z.array(z.string()),
+ }),
+ }),
+ })
+ .optional(),
+});
diff --git a/packages/server/src/emails/emails/build-failed.tsx b/packages/server/src/emails/emails/build-failed.tsx
index b3d999192..79e7b718d 100644
--- a/packages/server/src/emails/emails/build-failed.tsx
+++ b/packages/server/src/emails/emails/build-failed.tsx
@@ -12,7 +12,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
export type TemplateProps = {
projectName: string;
diff --git a/packages/server/src/emails/emails/build-success.tsx b/packages/server/src/emails/emails/build-success.tsx
index eadf7c44b..d9e500ab9 100644
--- a/packages/server/src/emails/emails/build-success.tsx
+++ b/packages/server/src/emails/emails/build-success.tsx
@@ -12,7 +12,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
export type TemplateProps = {
projectName: string;
diff --git a/packages/server/src/emails/emails/database-backup.tsx b/packages/server/src/emails/emails/database-backup.tsx
index 2bdf944c3..754d4d982 100644
--- a/packages/server/src/emails/emails/database-backup.tsx
+++ b/packages/server/src/emails/emails/database-backup.tsx
@@ -10,7 +10,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
export type TemplateProps = {
projectName: string;
diff --git a/packages/server/src/emails/emails/docker-cleanup.tsx b/packages/server/src/emails/emails/docker-cleanup.tsx
index 05d93ed75..985406ae0 100644
--- a/packages/server/src/emails/emails/docker-cleanup.tsx
+++ b/packages/server/src/emails/emails/docker-cleanup.tsx
@@ -1,6 +1,5 @@
import {
Body,
- Button,
Container,
Head,
Heading,
@@ -11,7 +10,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
export type TemplateProps = {
message: string;
diff --git a/packages/server/src/emails/emails/dokploy-restart.tsx b/packages/server/src/emails/emails/dokploy-restart.tsx
index 1ad3d6004..db4edd69c 100644
--- a/packages/server/src/emails/emails/dokploy-restart.tsx
+++ b/packages/server/src/emails/emails/dokploy-restart.tsx
@@ -10,7 +10,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
export type TemplateProps = {
date: string;
diff --git a/packages/server/src/emails/emails/notion-magic-link.tsx b/packages/server/src/emails/emails/notion-magic-link.tsx
index b2286c340..f4071ce00 100644
--- a/packages/server/src/emails/emails/notion-magic-link.tsx
+++ b/packages/server/src/emails/emails/notion-magic-link.tsx
@@ -9,7 +9,6 @@ import {
Preview,
Text,
} from "@react-email/components";
-import * as React from "react";
interface NotionMagicLinkEmailProps {
loginCode?: string;
diff --git a/packages/server/src/emails/emails/plaid-verify-identity.tsx b/packages/server/src/emails/emails/plaid-verify-identity.tsx
index 650ab4866..88cf893d7 100644
--- a/packages/server/src/emails/emails/plaid-verify-identity.tsx
+++ b/packages/server/src/emails/emails/plaid-verify-identity.tsx
@@ -9,7 +9,6 @@ import {
Section,
Text,
} from "@react-email/components";
-import * as React from "react";
interface PlaidVerifyIdentityEmailProps {
validationCode?: string;
diff --git a/packages/server/src/emails/emails/stripe-welcome.tsx b/packages/server/src/emails/emails/stripe-welcome.tsx
index 9377853be..dbf02ea0e 100644
--- a/packages/server/src/emails/emails/stripe-welcome.tsx
+++ b/packages/server/src/emails/emails/stripe-welcome.tsx
@@ -11,7 +11,6 @@ import {
Section,
Text,
} from "@react-email/components";
-import * as React from "react";
const baseUrl = process.env.VERCEL_URL!;
diff --git a/packages/server/src/emails/emails/vercel-invite-user.tsx b/packages/server/src/emails/emails/vercel-invite-user.tsx
index 53b31987c..79f50cd71 100644
--- a/packages/server/src/emails/emails/vercel-invite-user.tsx
+++ b/packages/server/src/emails/emails/vercel-invite-user.tsx
@@ -15,7 +15,6 @@ import {
Tailwind,
Text,
} from "@react-email/components";
-import * as React from "react";
interface VercelInviteUserEmailProps {
username?: string;
diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts
index d48e6ea8a..f74b8d9d0 100644
--- a/packages/server/src/index.ts
+++ b/packages/server/src/index.ts
@@ -1,7 +1,4 @@
-export * from "./auth/auth";
-export * from "./auth/token";
export * from "./auth/random-password";
-// export * from "./db";
export * from "./services/admin";
export * from "./services/user";
export * from "./services/project";
@@ -30,7 +27,6 @@ export * from "./services/ssh-key";
export * from "./services/git-provider";
export * from "./services/bitbucket";
export * from "./services/github";
-export * from "./services/auth";
export * from "./services/gitlab";
export * from "./services/server";
export * from "./services/application";
@@ -39,6 +35,7 @@ export * from "./setup/config-paths";
export * from "./setup/postgres-setup";
export * from "./setup/redis-setup";
export * from "./setup/server-setup";
+export * from "./setup/monitoring-setup";
export * from "./setup/setup";
export * from "./setup/traefik-setup";
export * from "./setup/server-validate";
@@ -57,6 +54,7 @@ export * from "./utils/notifications/database-backup";
export * from "./utils/notifications/dokploy-restart";
export * from "./utils/notifications/utils";
export * from "./utils/notifications/docker-cleanup";
+export * from "./utils/notifications/server-threshold";
export * from "./utils/builders/index";
export * from "./utils/builders/compose";
@@ -71,6 +69,7 @@ export * from "./utils/builders/utils";
export * from "./utils/cluster/upload";
export * from "./utils/docker/compose";
+export * from "./utils/docker/collision";
export * from "./utils/docker/domain";
export * from "./utils/docker/utils";
export * from "./utils/docker/types";
@@ -110,8 +109,10 @@ export * from "./utils/access-log/types";
export * from "./utils/access-log/utils";
export * from "./constants/index";
-export * from "./monitoring/utilts";
+export * from "./monitoring/utils";
export * from "./db/validations/domain";
export * from "./db/validations/index";
export * from "./utils/gpu-setup";
+
+export * from "./lib/auth";
diff --git a/packages/server/src/lib/auth.ts b/packages/server/src/lib/auth.ts
new file mode 100644
index 000000000..1efa17300
--- /dev/null
+++ b/packages/server/src/lib/auth.ts
@@ -0,0 +1,304 @@
+import type { IncomingMessage } from "node:http";
+import * as bcrypt from "bcrypt";
+import { betterAuth } from "better-auth";
+import { drizzleAdapter } from "better-auth/adapters/drizzle";
+import { organization, twoFactor, apiKey } from "better-auth/plugins";
+import { and, desc, eq } from "drizzle-orm";
+import { db } from "../db";
+import * as schema from "../db/schema";
+import { sendEmail } from "../verification/send-verification-email";
+import { IS_CLOUD } from "../constants";
+
+const { handler, api } = betterAuth({
+ database: drizzleAdapter(db, {
+ provider: "pg",
+ schema: schema,
+ }),
+ logger: {
+ disabled: process.env.NODE_ENV === "production",
+ },
+ appName: "Dokploy",
+ socialProviders: {
+ github: {
+ clientId: process.env.GITHUB_CLIENT_ID as string,
+ clientSecret: process.env.GITHUB_CLIENT_SECRET as string,
+ },
+ google: {
+ clientId: process.env.GOOGLE_CLIENT_ID as string,
+ clientSecret: process.env.GOOGLE_CLIENT_SECRET as string,
+ },
+ },
+ emailVerification: {
+ sendOnSignUp: true,
+ autoSignInAfterVerification: true,
+ sendVerificationEmail: async ({ user, url }) => {
+ if (IS_CLOUD) {
+ await sendEmail({
+ email: user.email,
+ subject: "Verify your email",
+ text: `
+ Click the link to verify your email: Verify Email
+ `,
+ });
+ }
+ },
+ },
+ emailAndPassword: {
+ enabled: true,
+ autoSignIn: !IS_CLOUD,
+ requireEmailVerification: IS_CLOUD,
+ password: {
+ async hash(password) {
+ return bcrypt.hashSync(password, 10);
+ },
+ async verify({ hash, password }) {
+ return bcrypt.compareSync(password, hash);
+ },
+ },
+ sendResetPassword: async ({ user, url }) => {
+ await sendEmail({
+ email: user.email,
+ subject: "Reset your password",
+ text: `
+ Click the link to reset your password: Reset Password
+ `,
+ });
+ },
+ },
+ databaseHooks: {
+ user: {
+ create: {
+ after: async (user) => {
+ const isAdminPresent = await db.query.member.findFirst({
+ where: eq(schema.member.role, "owner"),
+ });
+
+ if (IS_CLOUD || !isAdminPresent) {
+ await db.transaction(async (tx) => {
+ const organization = await tx
+ .insert(schema.organization)
+ .values({
+ name: "My Organization",
+ ownerId: user.id,
+ createdAt: new Date(),
+ })
+ .returning()
+ .then((res) => res[0]);
+
+ await tx.insert(schema.member).values({
+ userId: user.id,
+ organizationId: organization?.id || "",
+ role: "owner",
+ createdAt: new Date(),
+ });
+ });
+ }
+ },
+ },
+ },
+ session: {
+ create: {
+ before: async (session) => {
+ const member = await db.query.member.findFirst({
+ where: eq(schema.member.userId, session.userId),
+ orderBy: desc(schema.member.createdAt),
+ with: {
+ organization: true,
+ },
+ });
+
+ return {
+ data: {
+ ...session,
+ activeOrganizationId: member?.organization.id,
+ },
+ };
+ },
+ },
+ },
+ },
+ user: {
+ modelName: "users_temp",
+ additionalFields: {
+ role: {
+ type: "string",
+ // required: true,
+ input: false,
+ },
+ ownerId: {
+ type: "string",
+ // required: true,
+ input: false,
+ },
+ },
+ },
+
+ plugins: [
+ apiKey({
+ enableMetadata: true,
+ }),
+ twoFactor(),
+ organization({
+ async sendInvitationEmail(data, _request) {
+ if (IS_CLOUD) {
+ const host =
+ process.env.NODE_ENV === "development"
+ ? "http://localhost:3000"
+ : "https://dokploy.com";
+ const inviteLink = `${host}/invitation?token=${data.id}`;
+
+ await sendEmail({
+ email: data.email,
+ subject: "Invitation to join organization",
+ text: `
+ You are invited to join ${data.organization.name} on Dokploy. Click the link to accept the invitation: Accept Invitation
+ `,
+ });
+ }
+ },
+ }),
+ ],
+});
+
+export const auth = {
+ handler,
+ createApiKey: api.createApiKey,
+};
+
+export const validateRequest = async (request: IncomingMessage) => {
+ const apiKey = request.headers["x-api-key"] as string;
+ if (apiKey) {
+ try {
+ const { valid, key, error } = await api.verifyApiKey({
+ body: {
+ key: apiKey,
+ },
+ });
+
+ if (error) {
+ throw new Error(error.message || "Error verifying API key");
+ }
+ if (!valid || !key) {
+ return {
+ session: null,
+ user: null,
+ };
+ }
+
+ const apiKeyRecord = await db.query.apikey.findFirst({
+ where: eq(schema.apikey.id, key.id),
+ with: {
+ user: true,
+ },
+ });
+
+ if (!apiKeyRecord) {
+ return {
+ session: null,
+ user: null,
+ };
+ }
+
+ const organizationId = JSON.parse(
+ apiKeyRecord.metadata || "{}",
+ ).organizationId;
+
+ if (!organizationId) {
+ return {
+ session: null,
+ user: null,
+ };
+ }
+
+ const member = await db.query.member.findFirst({
+ where: and(
+ eq(schema.member.userId, apiKeyRecord.user.id),
+ eq(schema.member.organizationId, organizationId),
+ ),
+ with: {
+ organization: true,
+ },
+ });
+
+ const {
+ id,
+ name,
+ email,
+ emailVerified,
+ image,
+ createdAt,
+ updatedAt,
+ twoFactorEnabled,
+ } = apiKeyRecord.user;
+
+ const mockSession = {
+ session: {
+ user: {
+ id: apiKeyRecord.user.id,
+ email: apiKeyRecord.user.email,
+ name: apiKeyRecord.user.name,
+ },
+ activeOrganizationId: organizationId || "",
+ },
+ user: {
+ id,
+ name,
+ email,
+ emailVerified,
+ image,
+ createdAt,
+ updatedAt,
+ twoFactorEnabled,
+ role: member?.role || "member",
+ ownerId: member?.organization.ownerId || apiKeyRecord.user.id,
+ },
+ };
+
+ return mockSession;
+ } catch (error) {
+ console.error("Error verifying API key", error);
+ return {
+ session: null,
+ user: null,
+ };
+ }
+ }
+
+ // If no API key, proceed with normal session validation
+ const session = await api.getSession({
+ headers: new Headers({
+ cookie: request.headers.cookie || "",
+ }),
+ });
+
+ if (!session?.session || !session.user) {
+ return {
+ session: null,
+ user: null,
+ };
+ }
+
+ if (session?.user) {
+ const member = await db.query.member.findFirst({
+ where: and(
+ eq(schema.member.userId, session.user.id),
+ eq(
+ schema.member.organizationId,
+ session.session.activeOrganizationId || "",
+ ),
+ ),
+ with: {
+ organization: true,
+ },
+ });
+
+ session.user.role = member?.role || "member";
+ if (member) {
+ session.user.ownerId = member.organization.ownerId;
+ } else {
+ session.user.ownerId = session.user.id;
+ }
+ }
+
+ return session;
+};
diff --git a/packages/server/src/monitoring/utilts.ts b/packages/server/src/monitoring/utils.ts
similarity index 52%
rename from packages/server/src/monitoring/utilts.ts
rename to packages/server/src/monitoring/utils.ts
index f67d57050..147ade0ad 100644
--- a/packages/server/src/monitoring/utilts.ts
+++ b/packages/server/src/monitoring/utils.ts
@@ -1,10 +1,19 @@
import { promises } from "node:fs";
-import type Dockerode from "dockerode";
import osUtils from "node-os-utils";
import { paths } from "../constants";
+export interface Container {
+ BlockIO: string;
+ CPUPerc: string;
+ Container: string;
+ ID: string;
+ MemPerc: string;
+ MemUsage: string;
+ Name: string;
+ NetIO: string;
+}
export const recordAdvancedStats = async (
- stats: Dockerode.ContainerStats,
+ stats: Container,
appName: string,
) => {
const { MONITORING_PATH } = paths();
@@ -12,29 +21,20 @@ export const recordAdvancedStats = async (
await promises.mkdir(path, { recursive: true });
- const cpuPercent = calculateCpuUsagePercent(
- stats.cpu_stats,
- stats.precpu_stats,
- );
- const memoryStats = calculateMemoryStats(stats.memory_stats);
- const blockIO = calculateBlockIO(stats.blkio_stats);
- const networkUsage = calculateNetworkUsage(stats.networks);
-
- await updateStatsFile(appName, "cpu", cpuPercent);
+ await updateStatsFile(appName, "cpu", stats.CPUPerc);
await updateStatsFile(appName, "memory", {
- used: memoryStats.used,
- free: memoryStats.free,
- usedPercentage: memoryStats.usedPercentage,
- total: memoryStats.total,
+ used: stats.MemUsage.split(" ")[0],
+ total: stats.MemUsage.split(" ")[2],
});
+
await updateStatsFile(appName, "block", {
- readMb: blockIO.readMb,
- writeMb: blockIO.writeMb,
+ readMb: stats.BlockIO.split(" ")[0],
+ writeMb: stats.BlockIO.split(" ")[2],
});
await updateStatsFile(appName, "network", {
- inputMb: networkUsage.inputMb,
- outputMb: networkUsage.outputMb,
+ inputMb: stats.NetIO.split(" ")[0],
+ outputMb: stats.NetIO.split(" ")[2],
});
if (appName === "dokploy") {
@@ -73,7 +73,7 @@ export const readStatsFile = async (
const filePath = `${MONITORING_PATH}/${appName}/${statType}.json`;
const data = await promises.readFile(filePath, "utf-8");
return JSON.parse(data);
- } catch (error) {
+ } catch (_error) {
return [];
}
};
@@ -108,7 +108,7 @@ export const readLastValueStatsFile = async (
const data = await promises.readFile(filePath, "utf-8");
const stats = JSON.parse(data);
return stats[stats.length - 1] || null;
- } catch (error) {
+ } catch (_error) {
return null;
}
};
@@ -122,77 +122,3 @@ export const getLastAdvancedStatsFile = async (appName: string) => {
block: await readLastValueStatsFile(appName, "block"),
};
};
-
-const calculateCpuUsagePercent = (
- cpu_stats: Dockerode.ContainerStats["cpu_stats"],
- precpu_stats: Dockerode.ContainerStats["precpu_stats"],
-) => {
- const cpuDelta =
- cpu_stats.cpu_usage.total_usage - precpu_stats.cpu_usage.total_usage;
- const systemDelta =
- cpu_stats.system_cpu_usage - precpu_stats.system_cpu_usage;
-
- const numberCpus =
- cpu_stats.online_cpus ||
- (cpu_stats.cpu_usage.percpu_usage
- ? cpu_stats.cpu_usage.percpu_usage.length
- : 1);
-
- if (systemDelta > 0 && cpuDelta > 0) {
- return (cpuDelta / systemDelta) * numberCpus * 100.0;
- }
- return 0;
-};
-
-const calculateMemoryStats = (
- memory_stats: Dockerode.ContainerStats["memory_stats"],
-) => {
- const usedMemory = memory_stats.usage - (memory_stats.stats.cache || 0);
- const availableMemory = memory_stats.limit;
- const memoryUsedPercentage = (usedMemory / availableMemory) * 100.0;
-
- return {
- used: usedMemory,
- free: availableMemory - usedMemory,
- usedPercentage: memoryUsedPercentage,
- total: availableMemory,
- };
-};
-const calculateBlockIO = (
- blkio_stats: Dockerode.ContainerStats["blkio_stats"],
-) => {
- let readIO = 0;
- let writeIO = 0;
- if (blkio_stats?.io_service_bytes_recursive) {
- for (const io of blkio_stats.io_service_bytes_recursive) {
- if (io.op === "read") {
- readIO += io.value;
- } else if (io.op === "write") {
- writeIO += io.value;
- }
- }
- }
- return {
- readMb: readIO / (1024 * 1024),
- writeMb: writeIO / (1024 * 1024),
- };
-};
-
-const calculateNetworkUsage = (
- networks: Dockerode.ContainerStats["networks"],
-) => {
- let totalRx = 0;
- let totalTx = 0;
-
- const stats = Object.keys(networks);
-
- for (const interfaceName of stats) {
- const net = networks[interfaceName];
- totalRx += net?.rx_bytes || 0;
- totalTx += net?.tx_bytes || 0;
- }
- return {
- inputMb: totalRx / (1024 * 1024),
- outputMb: totalTx / (1024 * 1024),
- };
-};
diff --git a/packages/server/src/services/admin.ts b/packages/server/src/services/admin.ts
index 3502395ec..3509868be 100644
--- a/packages/server/src/services/admin.ts
+++ b/packages/server/src/services/admin.ts
@@ -1,108 +1,56 @@
-import { randomBytes } from "node:crypto";
import { db } from "@dokploy/server/db";
import {
- admins,
- type apiCreateUserInvitation,
- auth,
- users,
+ invitation,
+ member,
+ organization,
+ users_temp,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
-import * as bcrypt from "bcrypt";
import { eq } from "drizzle-orm";
import { IS_CLOUD } from "../constants";
-export type Admin = typeof admins.$inferSelect;
-export const createInvitation = async (
- input: typeof apiCreateUserInvitation._type,
- adminId: string,
-) => {
- await db.transaction(async (tx) => {
- const result = await tx
- .insert(auth)
- .values({
- email: input.email.toLowerCase(),
- rol: "user",
- password: bcrypt.hashSync("01231203012312", 10),
- })
- .returning()
- .then((res) => res[0]);
-
- if (!result) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the user",
- });
- }
- const expiresIn24Hours = new Date();
- expiresIn24Hours.setDate(expiresIn24Hours.getDate() + 1);
- const token = randomBytes(32).toString("hex");
- await tx
- .insert(users)
- .values({
- adminId: adminId,
- authId: result.id,
- token,
- expirationDate: expiresIn24Hours.toISOString(),
- })
- .returning();
+export const findUserById = async (userId: string) => {
+ const user = await db.query.users_temp.findFirst({
+ where: eq(users_temp.id, userId),
+ // with: {
+ // account: true,
+ // },
});
-};
-
-export const findAdminById = async (adminId: string) => {
- const admin = await db.query.admins.findFirst({
- where: eq(admins.adminId, adminId),
- });
- if (!admin) {
+ if (!user) {
throw new TRPCError({
code: "NOT_FOUND",
- message: "Admin not found",
+ message: "User not found",
});
}
- return admin;
+ return user;
};
-export const updateAdmin = async (
- authId: string,
- adminData: Partial,
-) => {
- const admin = await db
- .update(admins)
- .set({
- ...adminData,
- })
- .where(eq(admins.authId, authId))
- .returning()
- .then((res) => res[0]);
-
- return admin;
+export const findOrganizationById = async (organizationId: string) => {
+ const organizationResult = await db.query.organization.findFirst({
+ where: eq(organization.id, organizationId),
+ });
+ return organizationResult;
};
export const isAdminPresent = async () => {
- const admin = await db.query.admins.findFirst();
+ const admin = await db.query.member.findFirst({
+ where: eq(member.role, "owner"),
+ });
+
if (!admin) {
return false;
}
return true;
};
-export const findAdminByAuthId = async (authId: string) => {
- const admin = await db.query.admins.findFirst({
- where: eq(admins.authId, authId),
+export const findAdmin = async () => {
+ const admin = await db.query.member.findFirst({
+ where: eq(member.role, "owner"),
with: {
- users: true,
+ user: true,
},
});
- if (!admin) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Admin not found",
- });
- }
- return admin;
-};
-export const findAdmin = async () => {
- const admin = await db.query.admins.findFirst({});
if (!admin) {
throw new TRPCError({
code: "NOT_FOUND",
@@ -113,14 +61,15 @@ export const findAdmin = async () => {
};
export const getUserByToken = async (token: string) => {
- const user = await db.query.users.findFirst({
- where: eq(users.token, token),
- with: {
- auth: {
- columns: {
- password: false,
- },
- },
+ const user = await db.query.invitation.findFirst({
+ where: eq(invitation.id, token),
+ columns: {
+ id: true,
+ email: true,
+ status: true,
+ expiresAt: true,
+ role: true,
+ inviterId: true,
},
});
@@ -130,34 +79,23 @@ export const getUserByToken = async (token: string) => {
message: "Invitation not found",
});
}
+
+ const userAlreadyExists = await db.query.users_temp.findFirst({
+ where: eq(users_temp.email, user?.email || ""),
+ });
+
+ const { expiresAt, ...rest } = user;
return {
- ...user,
- isExpired: user.isRegistered,
+ ...rest,
+ isExpired: user.expiresAt < new Date(),
+ userAlreadyExists: !!userAlreadyExists,
};
};
-export const removeUserByAuthId = async (authId: string) => {
+export const removeUserById = async (userId: string) => {
await db
- .delete(auth)
- .where(eq(auth.id, authId))
- .returning()
- .then((res) => res[0]);
-};
-
-export const removeAdminByAuthId = async (authId: string) => {
- const admin = await findAdminByAuthId(authId);
- if (!admin) return null;
-
- // First delete all associated users
- const users = admin.users;
-
- for (const user of users) {
- await removeUserByAuthId(user.authId);
- }
- // Then delete the auth record which will cascade delete the admin
- return await db
- .delete(auth)
- .where(eq(auth.id, authId))
+ .delete(users_temp)
+ .where(eq(users_temp.id, userId))
.returning()
.then((res) => res[0]);
};
@@ -168,8 +106,8 @@ export const getDokployUrl = async () => {
}
const admin = await findAdmin();
- if (admin.host) {
- return `https://${admin.host}`;
+ if (admin.user.host) {
+ return `https://${admin.user.host}`;
}
- return `http://${admin.serverIp}:${process.env.PORT}`;
+ return `http://${admin.user.serverIp}:${process.env.PORT}`;
};
diff --git a/packages/server/src/services/application.ts b/packages/server/src/services/application.ts
index 8de9b5ba5..425a6adbc 100644
--- a/packages/server/src/services/application.ts
+++ b/packages/server/src/services/application.ts
@@ -4,9 +4,8 @@ import {
type apiCreateApplication,
applications,
buildAppName,
- cleanAppName,
} from "@dokploy/server/db/schema";
-import { getAdvancedStats } from "@dokploy/server/monitoring/utilts";
+import { getAdvancedStats } from "@dokploy/server/monitoring/utils";
import {
buildApplication,
getBuildCommand,
@@ -28,7 +27,6 @@ import {
getCustomGitCloneCommand,
} from "@dokploy/server/utils/providers/git";
import {
- authGithub,
cloneGithubRepository,
getGithubCloneCommand,
} from "@dokploy/server/utils/providers/github";
@@ -175,6 +173,7 @@ export const deployApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
+
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
const deployment = await createDeployment({
applicationId: applicationId,
@@ -183,6 +182,12 @@ export const deployApplication = async ({
});
try {
+ // const admin = await findUserById(application.project.userId);
+
+ // if (admin.cleanupCacheApplications) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
+
if (application.sourceType === "github") {
await cloneGithubRepository({
...application,
@@ -212,7 +217,7 @@ export const deployApplication = async ({
applicationName: application.name,
applicationType: "application",
buildLink,
- adminId: application.project.adminId,
+ organizationId: application.project.organizationId,
domains: application.domains,
});
} catch (error) {
@@ -225,7 +230,7 @@ export const deployApplication = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- adminId: application.project.adminId,
+ organizationId: application.project.organizationId,
});
throw error;
@@ -244,6 +249,7 @@ export const rebuildApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
+
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
@@ -251,6 +257,11 @@ export const rebuildApplication = async ({
});
try {
+ // const admin = await findUserById(application.project.userId);
+
+ // if (admin.cleanupCacheApplications) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
if (application.sourceType === "github") {
await buildApplication(application, deployment.logPath);
} else if (application.sourceType === "gitlab") {
@@ -285,6 +296,7 @@ export const deployRemoteApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
+
const buildLink = `${await getDokployUrl()}/dashboard/project/${application.projectId}/services/application/${application.applicationId}?tab=deployments`;
const deployment = await createDeployment({
applicationId: applicationId,
@@ -294,6 +306,11 @@ export const deployRemoteApplication = async ({
try {
if (application.serverId) {
+ // const admin = await findUserById(application.project.userId);
+
+ // if (admin.cleanupCacheApplications) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
let command = "set -e;";
if (application.sourceType === "github") {
command += await getGithubCloneCommand({
@@ -332,7 +349,7 @@ export const deployRemoteApplication = async ({
applicationName: application.name,
applicationType: "application",
buildLink,
- adminId: application.project.adminId,
+ organizationId: application.project.organizationId,
domains: application.domains,
});
} catch (error) {
@@ -356,17 +373,9 @@ export const deployRemoteApplication = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- adminId: application.project.adminId,
+ organizationId: application.project.organizationId,
});
- console.log(
- "Error on ",
- application.buildType,
- "/",
- application.sourceType,
- error,
- );
-
throw error;
}
@@ -385,6 +394,7 @@ export const deployPreviewApplication = async ({
previewDeploymentId: string;
}) => {
const application = await findApplicationById(applicationId);
+
const deployment = await createDeploymentPreview({
title: titleLog,
description: descriptionLog,
@@ -438,9 +448,15 @@ export const deployPreviewApplication = async ({
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
});
application.appName = previewDeployment.appName;
- application.env = application.previewEnv;
+ application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`;
application.buildArgs = application.previewBuildArgs;
+ // const admin = await findUserById(application.project.userId);
+
+ // if (admin.cleanupCacheOnPreviews) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
+
if (application.sourceType === "github") {
await cloneGithubRepository({
...application,
@@ -450,7 +466,6 @@ export const deployPreviewApplication = async ({
});
await buildApplication(application, deployment.logPath);
}
- // 4eef09efc46009187d668cf1c25f768d0bde4f91
const successComment = getIssueComment(
application.name,
"success",
@@ -492,6 +507,7 @@ export const deployRemotePreviewApplication = async ({
previewDeploymentId: string;
}) => {
const application = await findApplicationById(applicationId);
+
const deployment = await createDeploymentPreview({
title: titleLog,
description: descriptionLog,
@@ -545,14 +561,21 @@ export const deployRemotePreviewApplication = async ({
body: `### Dokploy Preview Deployment\n\n${buildingComment}`,
});
application.appName = previewDeployment.appName;
- application.env = application.previewEnv;
+ application.env = `${application.previewEnv}\nDOKPLOY_DEPLOY_URL=${previewDeployment?.domain}`;
application.buildArgs = application.previewBuildArgs;
if (application.serverId) {
+ // const admin = await findUserById(application.project.userId);
+
+ // if (admin.cleanupCacheOnPreviews) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
let command = "set -e;";
if (application.sourceType === "github") {
command += await getGithubCloneCommand({
...application,
+ appName: previewDeployment.appName,
+ branch: previewDeployment.branch,
serverId: application.serverId,
logPath: deployment.logPath,
});
@@ -602,6 +625,7 @@ export const rebuildRemoteApplication = async ({
descriptionLog: string;
}) => {
const application = await findApplicationById(applicationId);
+
const deployment = await createDeployment({
applicationId: applicationId,
title: titleLog,
@@ -610,6 +634,11 @@ export const rebuildRemoteApplication = async ({
try {
if (application.serverId) {
+ // const admin = await findUserById(application.project.userId);
+
+ // if (admin.cleanupCacheApplications) {
+ // await cleanupFullDocker(application?.serverId);
+ // }
if (application.sourceType !== "docker") {
let command = "set -e;";
command += getBuildCommand(application, deployment.logPath);
diff --git a/packages/server/src/services/auth.ts b/packages/server/src/services/auth.ts
deleted file mode 100644
index 65a01c412..000000000
--- a/packages/server/src/services/auth.ts
+++ /dev/null
@@ -1,184 +0,0 @@
-import { randomBytes } from "node:crypto";
-import { db } from "@dokploy/server/db";
-import {
- admins,
- type apiCreateAdmin,
- type apiCreateUser,
- auth,
- users,
-} from "@dokploy/server/db/schema";
-import { getPublicIpWithFallback } from "@dokploy/server/wss/utils";
-import { TRPCError } from "@trpc/server";
-import * as bcrypt from "bcrypt";
-import { eq } from "drizzle-orm";
-import encode from "hi-base32";
-import { TOTP } from "otpauth";
-import QRCode from "qrcode";
-import { IS_CLOUD } from "../constants";
-
-export type Auth = typeof auth.$inferSelect;
-
-export const createAdmin = async (input: typeof apiCreateAdmin._type) => {
- return await db.transaction(async (tx) => {
- const hashedPassword = bcrypt.hashSync(input.password, 10);
- const newAuth = await tx
- .insert(auth)
- .values({
- email: input.email.toLowerCase(),
- password: hashedPassword,
- rol: "admin",
- })
- .returning()
- .then((res) => res[0]);
-
- if (!newAuth) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the user",
- });
- }
-
- await tx
- .insert(admins)
- .values({
- authId: newAuth.id,
- ...(!IS_CLOUD && {
- serverIp:
- process.env.ADVERTISE_ADDR || (await getPublicIpWithFallback()),
- }),
- })
- .returning();
-
- return newAuth;
- });
-};
-
-export const createUser = async (input: typeof apiCreateUser._type) => {
- return await db.transaction(async (tx) => {
- const hashedPassword = bcrypt.hashSync(input.password, 10);
- const res = await tx
- .update(auth)
- .set({
- password: hashedPassword,
- })
- .where(eq(auth.id, input.id))
- .returning()
- .then((res) => res[0]);
-
- if (!res) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Error creating the user",
- });
- }
-
- const user = await tx
- .update(users)
- .set({
- isRegistered: true,
- expirationDate: undefined,
- })
- .where(eq(users.token, input.token))
- .returning()
- .then((res) => res[0]);
-
- return user;
- });
-};
-
-export const findAuthByEmail = async (email: string) => {
- const result = await db.query.auth.findFirst({
- where: eq(auth.email, email),
- });
- if (!result) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "User not found",
- });
- }
- return result;
-};
-
-export const findAuthById = async (authId: string) => {
- const result = await db.query.auth.findFirst({
- where: eq(auth.id, authId),
- columns: {
- password: false,
- },
- });
- if (!result) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "Auth not found",
- });
- }
- return result;
-};
-
-export const updateAuthById = async (
- authId: string,
- authData: Partial,
-) => {
- const result = await db
- .update(auth)
- .set({
- ...authData,
- })
- .where(eq(auth.id, authId))
- .returning();
-
- return result[0];
-};
-
-export const generate2FASecret = async (authId: string) => {
- const auth = await findAuthById(authId);
-
- const base32_secret = generateBase32Secret();
-
- const totp = new TOTP({
- issuer: "Dokploy",
- label: `${auth?.email}`,
- algorithm: "SHA1",
- digits: 6,
- secret: base32_secret,
- });
-
- const otpauth_url = totp.toString();
-
- const qrUrl = await QRCode.toDataURL(otpauth_url);
-
- return {
- qrCodeUrl: qrUrl,
- secret: base32_secret,
- };
-};
-
-export const verify2FA = async (
- auth: Omit,
- secret: string,
- pin: string,
-) => {
- const totp = new TOTP({
- issuer: "Dokploy",
- label: `${auth?.email}`,
- algorithm: "SHA1",
- digits: 6,
- secret: secret,
- });
-
- const delta = totp.validate({ token: pin });
-
- if (delta === null) {
- throw new TRPCError({
- code: "BAD_REQUEST",
- message: "Invalid 2FA code",
- });
- }
- return auth;
-};
-
-const generateBase32Secret = () => {
- const buffer = randomBytes(15);
- const base32 = encode.encode(buffer).replace(/=/g, "").substring(0, 24);
- return base32;
-};
diff --git a/packages/server/src/services/backup.ts b/packages/server/src/services/backup.ts
index 70e37af4d..327057869 100644
--- a/packages/server/src/services/backup.ts
+++ b/packages/server/src/services/backup.ts
@@ -6,7 +6,7 @@ import { eq } from "drizzle-orm";
export type Backup = typeof backups.$inferSelect;
export type BackupSchedule = Awaited>;
-
+export type BackupScheduleList = Awaited>;
export const createBackup = async (input: typeof apiCreateBackup._type) => {
const newBackup = await db
.insert(backups)
@@ -69,3 +69,20 @@ export const removeBackupById = async (backupId: string) => {
return result[0];
};
+
+export const findBackupsByDbId = async (
+ id: string,
+ type: "postgres" | "mysql" | "mariadb" | "mongo",
+) => {
+ const result = await db.query.backups.findMany({
+ where: eq(backups[`${type}Id`], id),
+ with: {
+ postgres: true,
+ mysql: true,
+ mariadb: true,
+ mongo: true,
+ destination: true,
+ },
+ });
+ return result || [];
+};
diff --git a/packages/server/src/services/bitbucket.ts b/packages/server/src/services/bitbucket.ts
index 218071567..7b5be7d65 100644
--- a/packages/server/src/services/bitbucket.ts
+++ b/packages/server/src/services/bitbucket.ts
@@ -12,14 +12,14 @@ export type Bitbucket = typeof bitbucket.$inferSelect;
export const createBitbucket = async (
input: typeof apiCreateBitbucket._type,
- adminId: string,
+ organizationId: string,
) => {
return await db.transaction(async (tx) => {
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "bitbucket",
- adminId: adminId,
+ organizationId: organizationId,
name: input.name,
})
.returning()
@@ -74,12 +74,12 @@ export const updateBitbucket = async (
.where(eq(bitbucket.bitbucketId, bitbucketId))
.returning();
- if (input.name || input.adminId) {
+ if (input.name || input.organizationId) {
await tx
.update(gitProvider)
.set({
name: input.name,
- adminId: input.adminId,
+ organizationId: input.organizationId,
})
.where(eq(gitProvider.gitProviderId, input.gitProviderId))
.returning();
diff --git a/packages/server/src/services/certificate.ts b/packages/server/src/services/certificate.ts
index 231778621..f59f1c2aa 100644
--- a/packages/server/src/services/certificate.ts
+++ b/packages/server/src/services/certificate.ts
@@ -33,13 +33,13 @@ export const findCertificateById = async (certificateId: string) => {
export const createCertificate = async (
certificateData: z.infer,
- adminId: string,
+ organizationId: string,
) => {
const certificate = await db
.insert(certificates)
.values({
...certificateData,
- adminId: adminId,
+ organizationId: organizationId,
})
.returning();
diff --git a/packages/server/src/services/compose.ts b/packages/server/src/services/compose.ts
index 8561dd376..a3ebc26ce 100644
--- a/packages/server/src/services/compose.ts
+++ b/packages/server/src/services/compose.ts
@@ -3,7 +3,6 @@ import { paths } from "@dokploy/server/constants";
import { db } from "@dokploy/server/db";
import { type apiCreateCompose, compose } from "@dokploy/server/db/schema";
import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
-import { generatePassword } from "@dokploy/server/templates/utils";
import {
buildCompose,
getBuildComposeCommand,
@@ -206,6 +205,7 @@ export const deployCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
+
const buildLink = `${await getDokployUrl()}/dashboard/project/${
compose.projectId
}/services/compose/${compose.composeId}?tab=deployments`;
@@ -216,6 +216,10 @@ export const deployCompose = async ({
});
try {
+ // const admin = await findUserById(compose.project.userId);
+ // if (admin.cleanupCacheOnCompose) {
+ // await cleanupFullDocker(compose?.serverId);
+ // }
if (compose.sourceType === "github") {
await cloneGithubRepository({
...compose,
@@ -242,7 +246,7 @@ export const deployCompose = async ({
applicationName: compose.name,
applicationType: "compose",
buildLink,
- adminId: compose.project.adminId,
+ organizationId: compose.project.organizationId,
domains: compose.domains,
});
} catch (error) {
@@ -257,7 +261,7 @@ export const deployCompose = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- adminId: compose.project.adminId,
+ organizationId: compose.project.organizationId,
});
throw error;
}
@@ -273,6 +277,7 @@ export const rebuildCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
+
const deployment = await createDeploymentCompose({
composeId: composeId,
title: titleLog,
@@ -280,6 +285,10 @@ export const rebuildCompose = async ({
});
try {
+ // const admin = await findUserById(compose.project.userId);
+ // if (admin.cleanupCacheOnCompose) {
+ // await cleanupFullDocker(compose?.serverId);
+ // }
if (compose.serverId) {
await getBuildComposeCommand(compose, deployment.logPath);
} else {
@@ -311,6 +320,7 @@ export const deployRemoteCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
+
const buildLink = `${await getDokployUrl()}/dashboard/project/${
compose.projectId
}/services/compose/${compose.composeId}?tab=deployments`;
@@ -321,6 +331,10 @@ export const deployRemoteCompose = async ({
});
try {
if (compose.serverId) {
+ // const admin = await findUserById(compose.project.userId);
+ // if (admin.cleanupCacheOnCompose) {
+ // await cleanupFullDocker(compose?.serverId);
+ // }
let command = "set -e;";
if (compose.sourceType === "github") {
@@ -366,7 +380,7 @@ export const deployRemoteCompose = async ({
applicationName: compose.name,
applicationType: "compose",
buildLink,
- adminId: compose.project.adminId,
+ organizationId: compose.project.organizationId,
domains: compose.domains,
});
} catch (error) {
@@ -391,7 +405,7 @@ export const deployRemoteCompose = async ({
// @ts-ignore
errorMessage: error?.message || "Error building",
buildLink,
- adminId: compose.project.adminId,
+ organizationId: compose.project.organizationId,
});
throw error;
}
@@ -407,6 +421,7 @@ export const rebuildRemoteCompose = async ({
descriptionLog: string;
}) => {
const compose = await findComposeById(composeId);
+
const deployment = await createDeploymentCompose({
composeId: composeId,
title: titleLog,
@@ -414,6 +429,10 @@ export const rebuildRemoteCompose = async ({
});
try {
+ // const admin = await findUserById(compose.project.userId);
+ // if (admin.cleanupCacheOnCompose) {
+ // await cleanupFullDocker(compose?.serverId);
+ // }
if (compose.serverId) {
await getBuildComposeCommand(compose, deployment.logPath);
}
@@ -538,6 +557,17 @@ export const stopCompose = async (composeId: string) => {
}
}
+ if (compose.composeType === "stack") {
+ if (compose.serverId) {
+ await execAsyncRemote(
+ compose.serverId,
+ `docker stack rm ${compose.appName}`,
+ );
+ } else {
+ await execAsync(`docker stack rm ${compose.appName}`);
+ }
+ }
+
await updateCompose(composeId, {
composeStatus: "idle",
});
diff --git a/packages/server/src/services/deployment.ts b/packages/server/src/services/deployment.ts
index 0e55ea320..86d6c88e8 100644
--- a/packages/server/src/services/deployment.ts
+++ b/packages/server/src/services/deployment.ts
@@ -12,7 +12,7 @@ import {
import { removeDirectoryIfExistsContent } from "@dokploy/server/utils/filesystem/directory";
import { TRPCError } from "@trpc/server";
import { format } from "date-fns";
-import { and, desc, eq, isNull } from "drizzle-orm";
+import { desc, eq } from "drizzle-orm";
import {
type Application,
findApplicationById,
@@ -98,6 +98,17 @@ export const createDeployment = async (
}
return deploymentCreate[0];
} catch (error) {
+ await db
+ .insert(deployments)
+ .values({
+ applicationId: deployment.applicationId,
+ title: deployment.title || "Deployment",
+ status: "error",
+ logPath: "",
+ description: deployment.description || "",
+ errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
+ })
+ .returning();
await updateApplicationStatus(application.applicationId, "error");
console.log(error);
throw new TRPCError({
@@ -164,6 +175,17 @@ export const createDeploymentPreview = async (
}
return deploymentCreate[0];
} catch (error) {
+ await db
+ .insert(deployments)
+ .values({
+ previewDeploymentId: deployment.previewDeploymentId,
+ title: deployment.title || "Deployment",
+ status: "error",
+ logPath: "",
+ description: deployment.description || "",
+ errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
+ })
+ .returning();
await updatePreviewDeployment(deployment.previewDeploymentId, {
previewStatus: "error",
});
@@ -226,6 +248,17 @@ echo "Initializing deployment" >> ${logFilePath};
}
return deploymentCreate[0];
} catch (error) {
+ await db
+ .insert(deployments)
+ .values({
+ composeId: deployment.composeId,
+ title: deployment.title || "Deployment",
+ status: "error",
+ logPath: "",
+ description: deployment.description || "",
+ errorMessage: `An error have occured: ${error instanceof Error ? error.message : error}`,
+ })
+ .returning();
await updateCompose(compose.composeId, {
composeStatus: "error",
});
@@ -245,9 +278,11 @@ export const removeDeployment = async (deploymentId: string) => {
.returning();
return deployment[0];
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error creating the deployment";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error deleting this deployment",
+ message,
});
}
};
@@ -502,9 +537,11 @@ export const createServerDeployment = async (
}
return deploymentCreate[0];
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error creating the deployment";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error creating the deployment",
+ message,
});
}
};
diff --git a/packages/server/src/services/destination.ts b/packages/server/src/services/destination.ts
index 892c93541..e66f8695a 100644
--- a/packages/server/src/services/destination.ts
+++ b/packages/server/src/services/destination.ts
@@ -10,13 +10,13 @@ export type Destination = typeof destinations.$inferSelect;
export const createDestintation = async (
input: typeof apiCreateDestination._type,
- adminId: string,
+ organizationId: string,
) => {
const newDestination = await db
.insert(destinations)
.values({
...input,
- adminId: adminId,
+ organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
@@ -46,14 +46,14 @@ export const findDestinationById = async (destinationId: string) => {
export const removeDestinationById = async (
destinationId: string,
- adminId: string,
+ organizationId: string,
) => {
const result = await db
.delete(destinations)
.where(
and(
eq(destinations.destinationId, destinationId),
- eq(destinations.adminId, adminId),
+ eq(destinations.organizationId, organizationId),
),
)
.returning();
@@ -73,7 +73,7 @@ export const updateDestinationById = async (
.where(
and(
eq(destinations.destinationId, destinationId),
- eq(destinations.adminId, destinationData.adminId || ""),
+ eq(destinations.organizationId, destinationData.organizationId || ""),
),
)
.returning();
diff --git a/packages/server/src/services/docker.ts b/packages/server/src/services/docker.ts
index b7a5c4403..a4a3b0b5e 100644
--- a/packages/server/src/services/docker.ts
+++ b/packages/server/src/services/docker.ts
@@ -58,7 +58,11 @@ export const getContainers = async (serverId?: string | null) => {
serverId,
};
})
- .filter((container) => !container.name.includes("dokploy"));
+ .filter(
+ (container) =>
+ !container.name.includes("dokploy") ||
+ container.name.includes("dokploy-monitoring"),
+ );
return containers;
} catch (error) {
@@ -94,7 +98,7 @@ export const getConfig = async (
const config = JSON.parse(stdout);
return config;
- } catch (error) {}
+ } catch (_error) {}
};
export const getContainersByAppNameMatch = async (
@@ -152,7 +156,7 @@ export const getContainersByAppNameMatch = async (
});
return containers || [];
- } catch (error) {}
+ } catch (_error) {}
return [];
};
@@ -210,7 +214,7 @@ export const getStackContainersByAppName = async (
});
return containers || [];
- } catch (error) {}
+ } catch (_error) {}
return [];
};
@@ -270,7 +274,7 @@ export const getServiceContainersByAppName = async (
});
return containers || [];
- } catch (error) {}
+ } catch (_error) {}
return [];
};
@@ -321,7 +325,7 @@ export const getContainersByAppLabel = async (
});
return containers || [];
- } catch (error) {}
+ } catch (_error) {}
return [];
};
@@ -340,7 +344,7 @@ export const containerRestart = async (containerId: string) => {
const config = JSON.parse(stdout);
return config;
- } catch (error) {}
+ } catch (_error) {}
};
export const getSwarmNodes = async (serverId?: string) => {
@@ -369,7 +373,7 @@ export const getSwarmNodes = async (serverId?: string) => {
.split("\n")
.map((line) => JSON.parse(line));
return nodesArray;
- } catch (error) {}
+ } catch (_error) {}
};
export const getNodeInfo = async (nodeId: string, serverId?: string) => {
@@ -395,7 +399,7 @@ export const getNodeInfo = async (nodeId: string, serverId?: string) => {
const nodeInfo = JSON.parse(stdout);
return nodeInfo;
- } catch (error) {}
+ } catch (_error) {}
};
export const getNodeApplications = async (serverId?: string) => {
@@ -427,7 +431,7 @@ export const getNodeApplications = async (serverId?: string) => {
.filter((service) => !service.Name.startsWith("dokploy-"));
return appArray;
- } catch (error) {}
+ } catch (_error) {}
};
export const getApplicationInfo = async (
@@ -460,5 +464,5 @@ export const getApplicationInfo = async (
.map((line) => JSON.parse(line));
return appArray;
- } catch (error) {}
+ } catch (_error) {}
};
diff --git a/packages/server/src/services/domain.ts b/packages/server/src/services/domain.ts
index b99c4869d..d2e23c06b 100644
--- a/packages/server/src/services/domain.ts
+++ b/packages/server/src/services/domain.ts
@@ -4,7 +4,7 @@ import { manageDomain } from "@dokploy/server/utils/traefik/domain";
import { TRPCError } from "@trpc/server";
import { eq } from "drizzle-orm";
import { type apiCreateDomain, domains } from "../db/schema";
-import { findAdmin, findAdminById } from "./admin";
+import { findUserById } from "./admin";
import { findApplicationById } from "./application";
import { findServerById } from "./server";
@@ -40,7 +40,7 @@ export const createDomain = async (input: typeof apiCreateDomain._type) => {
export const generateTraefikMeDomain = async (
appName: string,
- adminId: string,
+ userId: string,
serverId?: string,
) => {
if (serverId) {
@@ -57,7 +57,7 @@ export const generateTraefikMeDomain = async (
projectName: appName,
});
}
- const admin = await findAdminById(adminId);
+ const admin = await findUserById(userId);
return generateRandomDomain({
serverIp: admin?.serverIp || "",
projectName: appName,
@@ -126,7 +126,6 @@ export const updateDomainById = async (
export const removeDomainById = async (domainId: string) => {
await findDomainById(domainId);
- // TODO: fix order
const result = await db
.delete(domains)
.where(eq(domains.domainId, domainId))
diff --git a/packages/server/src/services/github.ts b/packages/server/src/services/github.ts
index a5d9d8638..19deb2b24 100644
--- a/packages/server/src/services/github.ts
+++ b/packages/server/src/services/github.ts
@@ -12,14 +12,14 @@ import { updatePreviewDeployment } from "./preview-deployment";
export type Github = typeof github.$inferSelect;
export const createGithub = async (
input: typeof apiCreateGithub._type,
- adminId: string,
+ organizationId: string,
) => {
return await db.transaction(async (tx) => {
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "github",
- adminId: adminId,
+ organizationId: organizationId,
name: input.name,
})
.returning()
@@ -119,7 +119,7 @@ export const issueCommentExists = async ({
comment_id: comment_id,
});
return true;
- } catch (error) {
+ } catch (_error) {
return false;
}
};
diff --git a/packages/server/src/services/gitlab.ts b/packages/server/src/services/gitlab.ts
index 8e1362c94..fdca2775e 100644
--- a/packages/server/src/services/gitlab.ts
+++ b/packages/server/src/services/gitlab.ts
@@ -1,9 +1,7 @@
import { db } from "@dokploy/server/db";
import {
type apiCreateGitlab,
- type bitbucket,
gitProvider,
- type github,
gitlab,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
@@ -13,14 +11,14 @@ export type Gitlab = typeof gitlab.$inferSelect;
export const createGitlab = async (
input: typeof apiCreateGitlab._type,
- adminId: string,
+ organizationId: string,
) => {
return await db.transaction(async (tx) => {
const newGitProvider = await tx
.insert(gitProvider)
.values({
providerType: "gitlab",
- adminId: adminId,
+ organizationId: organizationId,
name: input.name,
})
.returning()
diff --git a/packages/server/src/services/mariadb.ts b/packages/server/src/services/mariadb.ts
index 8257b5875..00be29d6a 100644
--- a/packages/server/src/services/mariadb.ts
+++ b/packages/server/src/services/mariadb.ts
@@ -4,7 +4,7 @@ import {
backups,
mariadb,
} from "@dokploy/server/db/schema";
-import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
+import { buildAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildMariadb } from "@dokploy/server/utils/databases/mariadb";
import { pullImage } from "@dokploy/server/utils/docker/utils";
diff --git a/packages/server/src/services/mongo.ts b/packages/server/src/services/mongo.ts
index 031a60131..0ac4cc632 100644
--- a/packages/server/src/services/mongo.ts
+++ b/packages/server/src/services/mongo.ts
@@ -1,6 +1,6 @@
import { db } from "@dokploy/server/db";
import { type apiCreateMongo, backups, mongo } from "@dokploy/server/db/schema";
-import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
+import { buildAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildMongo } from "@dokploy/server/utils/databases/mongo";
import { pullImage } from "@dokploy/server/utils/docker/utils";
diff --git a/packages/server/src/services/mount.ts b/packages/server/src/services/mount.ts
index dd7bd3e9a..836feacec 100644
--- a/packages/server/src/services/mount.ts
+++ b/packages/server/src/services/mount.ts
@@ -64,7 +64,7 @@ export const createMount = async (input: typeof apiCreateMount._type) => {
console.log(error);
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error creating the mount",
+ message: `Error ${error instanceof Error ? error.message : error}`,
cause: error,
});
}
@@ -91,7 +91,7 @@ export const createFileMount = async (mountId: string) => {
console.log(`Error creating the file mount: ${error}`);
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error creating the mount",
+ message: `Error creating the mount ${error instanceof Error ? error.message : error}`,
cause: error,
});
}
@@ -123,8 +123,8 @@ export const updateMount = async (
mountId: string,
mountData: Partial,
) => {
- return await db.transaction(async (transaction) => {
- const mount = await db
+ return await db.transaction(async (tx) => {
+ const mount = await tx
.update(mounts)
.set({
...mountData,
@@ -211,7 +211,7 @@ export const deleteFileMount = async (mountId: string) => {
} else {
await removeFileOrDirectory(fullPath);
}
- } catch (error) {}
+ } catch (_error) {}
};
export const getBaseFilesPath = async (mountId: string) => {
diff --git a/packages/server/src/services/notification.ts b/packages/server/src/services/notification.ts
index 2b62b4574..16ba2085b 100644
--- a/packages/server/src/services/notification.ts
+++ b/packages/server/src/services/notification.ts
@@ -24,7 +24,7 @@ export type Notification = typeof notifications.$inferSelect;
export const createSlackNotification = async (
input: typeof apiCreateSlack._type,
- adminId: string,
+ organizationId: string,
) => {
await db.transaction(async (tx) => {
const newSlack = await tx
@@ -54,7 +54,8 @@ export const createSlackNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "slack",
- adminId: adminId,
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
})
.returning()
.then((value) => value[0]);
@@ -83,7 +84,8 @@ export const updateSlackNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
- adminId: input.adminId,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
@@ -112,7 +114,7 @@ export const updateSlackNotification = async (
export const createTelegramNotification = async (
input: typeof apiCreateTelegram._type,
- adminId: string,
+ organizationId: string,
) => {
await db.transaction(async (tx) => {
const newTelegram = await tx
@@ -120,6 +122,7 @@ export const createTelegramNotification = async (
.values({
botToken: input.botToken,
chatId: input.chatId,
+ messageThreadId: input.messageThreadId,
})
.returning()
.then((value) => value[0]);
@@ -142,7 +145,8 @@ export const createTelegramNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "telegram",
- adminId: adminId,
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
})
.returning()
.then((value) => value[0]);
@@ -171,7 +175,8 @@ export const updateTelegramNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
- adminId: input.adminId,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
@@ -189,6 +194,7 @@ export const updateTelegramNotification = async (
.set({
botToken: input.botToken,
chatId: input.chatId,
+ messageThreadId: input.messageThreadId,
})
.where(eq(telegram.telegramId, input.telegramId))
.returning()
@@ -200,7 +206,7 @@ export const updateTelegramNotification = async (
export const createDiscordNotification = async (
input: typeof apiCreateDiscord._type,
- adminId: string,
+ organizationId: string,
) => {
await db.transaction(async (tx) => {
const newDiscord = await tx
@@ -230,7 +236,8 @@ export const createDiscordNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "discord",
- adminId: adminId,
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
})
.returning()
.then((value) => value[0]);
@@ -259,7 +266,8 @@ export const updateDiscordNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
- adminId: input.adminId,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
@@ -288,7 +296,7 @@ export const updateDiscordNotification = async (
export const createEmailNotification = async (
input: typeof apiCreateEmail._type,
- adminId: string,
+ organizationId: string,
) => {
await db.transaction(async (tx) => {
const newEmail = await tx
@@ -322,7 +330,8 @@ export const createEmailNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "email",
- adminId: adminId,
+ organizationId: organizationId,
+ serverThreshold: input.serverThreshold,
})
.returning()
.then((value) => value[0]);
@@ -351,7 +360,8 @@ export const updateEmailNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
- adminId: input.adminId,
+ organizationId: input.organizationId,
+ serverThreshold: input.serverThreshold,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
@@ -384,7 +394,7 @@ export const updateEmailNotification = async (
export const createGotifyNotification = async (
input: typeof apiCreateGotify._type,
- adminId: string,
+ organizationId: string,
) => {
await db.transaction(async (tx) => {
const newGotify = await tx
@@ -416,7 +426,7 @@ export const createGotifyNotification = async (
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
notificationType: "gotify",
- adminId: adminId,
+ organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
@@ -445,7 +455,7 @@ export const updateGotifyNotification = async (
databaseBackup: input.databaseBackup,
dokployRestart: input.dokployRestart,
dockerCleanup: input.dockerCleanup,
- adminId: input.adminId,
+ organizationId: input.organizationId,
})
.where(eq(notifications.notificationId, input.notificationId))
.returning()
diff --git a/packages/server/src/services/postgres.ts b/packages/server/src/services/postgres.ts
index 682d3f78d..75b81c506 100644
--- a/packages/server/src/services/postgres.ts
+++ b/packages/server/src/services/postgres.ts
@@ -4,7 +4,7 @@ import {
backups,
postgres,
} from "@dokploy/server/db/schema";
-import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
+import { buildAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildPostgres } from "@dokploy/server/utils/databases/postgres";
import { pullImage } from "@dokploy/server/utils/docker/utils";
diff --git a/packages/server/src/services/preview-deployment.ts b/packages/server/src/services/preview-deployment.ts
index ab38c17ca..a1ffca4ba 100644
--- a/packages/server/src/services/preview-deployment.ts
+++ b/packages/server/src/services/preview-deployment.ts
@@ -2,23 +2,20 @@ import { db } from "@dokploy/server/db";
import {
type apiCreatePreviewDeployment,
deployments,
+ organization,
previewDeployments,
} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
import { and, desc, eq } from "drizzle-orm";
-import { slugify } from "../setup/server-setup";
-import { generatePassword, generateRandomDomain } from "../templates/utils";
+import { generatePassword } from "../templates/utils";
import { removeService } from "../utils/docker/utils";
import { removeDirectoryCode } from "../utils/filesystem/directory";
import { authGithub } from "../utils/providers/github";
import { removeTraefikConfig } from "../utils/traefik/application";
import { manageDomain } from "../utils/traefik/domain";
-import { findAdminById } from "./admin";
+import { findUserById } from "./admin";
import { findApplicationById } from "./application";
-import {
- removeDeployments,
- removeDeploymentsByPreviewDeploymentId,
-} from "./deployment";
+import { removeDeploymentsByPreviewDeploymentId } from "./deployment";
import { createDomain } from "./domain";
import { type Github, getIssueComment } from "./github";
@@ -106,13 +103,17 @@ export const removePreviewDeployment = async (previewDeploymentId: string) => {
for (const operation of cleanupOperations) {
try {
await operation();
- } catch (error) {}
+ } catch (_error) {}
}
return deployment[0];
} catch (error) {
+ const message =
+ error instanceof Error
+ ? error.message
+ : "Error deleting this preview deployment";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error deleting this preview deployment",
+ message,
});
}
};
@@ -154,11 +155,14 @@ export const createPreviewDeployment = async (
const application = await findApplicationById(schema.applicationId);
const appName = `preview-${application.appName}-${generatePassword(6)}`;
+ const org = await db.query.organization.findFirst({
+ where: eq(organization.id, application.project.organizationId),
+ });
const generateDomain = await generateWildcardDomain(
application.previewWildcard || "*.traefik.me",
appName,
application.server?.ipAddress || "",
- application.project.adminId,
+ org?.ownerId || "",
);
const octokit = authGithub(application?.github as Github);
@@ -250,7 +254,7 @@ const generateWildcardDomain = async (
baseDomain: string,
appName: string,
serverIp: string,
- adminId: string,
+ userId: string,
): Promise => {
if (!baseDomain.startsWith("*.")) {
throw new Error('The base domain must start with "*."');
@@ -268,7 +272,7 @@ const generateWildcardDomain = async (
}
if (!ip) {
- const admin = await findAdminById(adminId);
+ const admin = await findUserById(userId);
ip = admin?.serverIp || "";
}
diff --git a/packages/server/src/services/project.ts b/packages/server/src/services/project.ts
index adaa07ea0..b740834b5 100644
--- a/packages/server/src/services/project.ts
+++ b/packages/server/src/services/project.ts
@@ -16,13 +16,13 @@ export type Project = typeof projects.$inferSelect;
export const createProject = async (
input: typeof apiCreateProject._type,
- adminId: string,
+ organizationId: string,
) => {
const newProject = await db
.insert(projects)
.values({
...input,
- adminId: adminId,
+ organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
diff --git a/packages/server/src/services/redirect.ts b/packages/server/src/services/redirect.ts
index f16dbe428..1896105fe 100644
--- a/packages/server/src/services/redirect.ts
+++ b/packages/server/src/services/redirect.ts
@@ -6,7 +6,7 @@ import {
updateRedirectMiddleware,
} from "@dokploy/server/utils/traefik/redirect";
import { TRPCError } from "@trpc/server";
-import { desc, eq } from "drizzle-orm";
+import { eq } from "drizzle-orm";
import type { z } from "zod";
import { findApplicationById } from "./application";
export type Redirect = typeof redirects.$inferSelect;
@@ -114,9 +114,11 @@ export const updateRedirectById = async (
return redirect;
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error updating this redirect";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error updating this redirect",
+ message,
});
}
};
diff --git a/packages/server/src/services/redis.ts b/packages/server/src/services/redis.ts
index e0dbbe025..9f4a1f9e6 100644
--- a/packages/server/src/services/redis.ts
+++ b/packages/server/src/services/redis.ts
@@ -1,6 +1,6 @@
import { db } from "@dokploy/server/db";
import { type apiCreateRedis, redis } from "@dokploy/server/db/schema";
-import { buildAppName, cleanAppName } from "@dokploy/server/db/schema";
+import { buildAppName } from "@dokploy/server/db/schema";
import { generatePassword } from "@dokploy/server/templates/utils";
import { buildRedis } from "@dokploy/server/utils/databases/redis";
import { pullImage } from "@dokploy/server/utils/docker/utils";
diff --git a/packages/server/src/services/registry.ts b/packages/server/src/services/registry.ts
index 2bcf3a4a4..6468cd970 100644
--- a/packages/server/src/services/registry.ts
+++ b/packages/server/src/services/registry.ts
@@ -12,14 +12,14 @@ export type Registry = typeof registry.$inferSelect;
export const createRegistry = async (
input: typeof apiCreateRegistry._type,
- adminId: string,
+ organizationId: string,
) => {
return await db.transaction(async (tx) => {
const newRegistry = await tx
.insert(registry)
.values({
...input,
- adminId: adminId,
+ organizationId: organizationId,
})
.returning()
.then((value) => value[0]);
@@ -112,9 +112,11 @@ export const updateRegistry = async (
return response;
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error updating this registry";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error updating this registry",
+ message,
});
}
};
@@ -135,9 +137,11 @@ export const findRegistryById = async (registryId: string) => {
return registryResponse;
};
-export const findAllRegistryByAdminId = async (adminId: string) => {
+export const findAllRegistryByOrganizationId = async (
+ organizationId: string,
+) => {
const registryResponse = await db.query.registry.findMany({
- where: eq(registry.adminId, adminId),
+ where: eq(registry.organizationId, organizationId),
});
return registryResponse;
};
diff --git a/packages/server/src/services/security.ts b/packages/server/src/services/security.ts
index 5efca19fd..d6947b887 100644
--- a/packages/server/src/services/security.ts
+++ b/packages/server/src/services/security.ts
@@ -76,9 +76,11 @@ export const deleteSecurityById = async (securityId: string) => {
await removeSecurityMiddleware(application, result);
return result;
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error removing this security";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error removing this security",
+ message,
});
}
};
@@ -98,9 +100,11 @@ export const updateSecurityById = async (
return response[0];
} catch (error) {
+ const message =
+ error instanceof Error ? error.message : "Error updating this security";
throw new TRPCError({
code: "BAD_REQUEST",
- message: "Error updating this security",
+ message,
});
}
};
diff --git a/packages/server/src/services/server.ts b/packages/server/src/services/server.ts
index 081b19fad..a4d5c5d85 100644
--- a/packages/server/src/services/server.ts
+++ b/packages/server/src/services/server.ts
@@ -1,19 +1,24 @@
import { db } from "@dokploy/server/db";
-import { type apiCreateServer, server } from "@dokploy/server/db/schema";
+import {
+ type apiCreateServer,
+ organization,
+ server,
+} from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
-import { desc, eq } from "drizzle-orm";
+import { eq } from "drizzle-orm";
export type Server = typeof server.$inferSelect;
export const createServer = async (
input: typeof apiCreateServer._type,
- adminId: string,
+ organizationId: string,
) => {
const newServer = await db
.insert(server)
.values({
...input,
- adminId: adminId,
+ organizationId: organizationId,
+ createdAt: new Date().toISOString(),
})
.returning()
.then((value) => value[0]);
@@ -45,12 +50,16 @@ export const findServerById = async (serverId: string) => {
return currentServer;
};
-export const findServersByAdminId = async (adminId: string) => {
- const servers = await db.query.server.findMany({
- where: eq(server.adminId, adminId),
- orderBy: desc(server.createdAt),
+export const findServersByUserId = async (userId: string) => {
+ const orgs = await db.query.organization.findMany({
+ where: eq(organization.ownerId, userId),
+ with: {
+ servers: true,
+ },
});
+ const servers = orgs.flatMap((org) => org.servers);
+
return servers;
};
diff --git a/packages/server/src/services/settings.ts b/packages/server/src/services/settings.ts
index 37f7b2ee8..75613be02 100644
--- a/packages/server/src/services/settings.ts
+++ b/packages/server/src/services/settings.ts
@@ -5,7 +5,6 @@ import {
execAsync,
execAsyncRemote,
} from "@dokploy/server/utils/process/execAsync";
-// import packageInfo from "../../../package.json";
export interface IUpdateData {
latestVersion: string | null;
@@ -170,7 +169,6 @@ echo "$json_output"
const result = JSON.parse(stdout);
return result;
}
- const items = readdirSync(dirPath, { withFileTypes: true });
const stack = [dirPath];
const result: TreeDataItem[] = [];
@@ -213,3 +211,35 @@ echo "$json_output"
}
return result;
};
+
+export const cleanupFullDocker = async (serverId?: string | null) => {
+ const cleanupImages = "docker image prune --force";
+ const cleanupVolumes = "docker volume prune --force";
+ const cleanupContainers = "docker container prune --force";
+ const cleanupSystem = "docker system prune --force --volumes";
+ const cleanupBuilder = "docker builder prune --force";
+
+ try {
+ if (serverId) {
+ await execAsyncRemote(
+ serverId,
+ `
+ ${cleanupImages}
+ ${cleanupVolumes}
+ ${cleanupContainers}
+ ${cleanupSystem}
+ ${cleanupBuilder}
+ `,
+ );
+ }
+ await execAsync(`
+ ${cleanupImages}
+ ${cleanupVolumes}
+ ${cleanupContainers}
+ ${cleanupSystem}
+ ${cleanupBuilder}
+ `);
+ } catch (error) {
+ console.log(error);
+ }
+};
diff --git a/packages/server/src/services/user.ts b/packages/server/src/services/user.ts
index d8d9862c4..39ac95cef 100644
--- a/packages/server/src/services/user.ts
+++ b/packages/server/src/services/user.ts
@@ -1,80 +1,53 @@
import { db } from "@dokploy/server/db";
-import { users } from "@dokploy/server/db/schema";
+import { apikey, member, users_temp } from "@dokploy/server/db/schema";
import { TRPCError } from "@trpc/server";
-import { eq } from "drizzle-orm";
+import { and, eq } from "drizzle-orm";
+import { auth } from "../lib/auth";
-export type User = typeof users.$inferSelect;
+export type User = typeof users_temp.$inferSelect;
-export const findUserById = async (userId: string) => {
- const user = await db.query.users.findFirst({
- where: eq(users.userId, userId),
- });
- if (!user) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "User not found",
- });
- }
- return user;
-};
-
-export const findUserByAuthId = async (authId: string) => {
- const user = await db.query.users.findFirst({
- where: eq(users.authId, authId),
- with: {
- auth: true,
- },
- });
- if (!user) {
- throw new TRPCError({
- code: "NOT_FOUND",
- message: "User not found",
- });
- }
- return user;
-};
-
-export const findUsers = async (adminId: string) => {
- const currentUsers = await db.query.users.findMany({
- where: eq(users.adminId, adminId),
- with: {
- auth: {
- columns: {
- secret: false,
- },
- },
- },
- });
- return currentUsers;
-};
-
-export const addNewProject = async (authId: string, projectId: string) => {
- const user = await findUserByAuthId(authId);
+export const addNewProject = async (
+ userId: string,
+ projectId: string,
+ organizationId: string,
+) => {
+ const userR = await findMemberById(userId, organizationId);
await db
- .update(users)
+ .update(member)
.set({
- accessedProjects: [...user.accessedProjects, projectId],
+ accessedProjects: [...userR.accessedProjects, projectId],
})
- .where(eq(users.authId, authId));
+ .where(
+ and(eq(member.id, userR.id), eq(member.organizationId, organizationId)),
+ );
};
-export const addNewService = async (authId: string, serviceId: string) => {
- const user = await findUserByAuthId(authId);
+export const addNewService = async (
+ userId: string,
+ serviceId: string,
+ organizationId: string,
+) => {
+ const userR = await findMemberById(userId, organizationId);
await db
- .update(users)
+ .update(member)
.set({
- accessedServices: [...user.accessedServices, serviceId],
+ accessedServices: [...userR.accessedServices, serviceId],
})
- .where(eq(users.authId, authId));
+ .where(
+ and(eq(member.id, userR.id), eq(member.organizationId, organizationId)),
+ );
};
export const canPerformCreationService = async (
userId: string,
projectId: string,
+ organizationId: string,
) => {
- const { accessedProjects, canCreateServices } =
- await findUserByAuthId(userId);
+ const { accessedProjects, canCreateServices } = await findMemberById(
+ userId,
+ organizationId,
+ );
const haveAccessToProject = accessedProjects.includes(projectId);
if (canCreateServices && haveAccessToProject) {
@@ -87,8 +60,9 @@ export const canPerformCreationService = async (
export const canPerformAccessService = async (
userId: string,
serviceId: string,
+ organizationId: string,
) => {
- const { accessedServices } = await findUserByAuthId(userId);
+ const { accessedServices } = await findMemberById(userId, organizationId);
const haveAccessToService = accessedServices.includes(serviceId);
if (haveAccessToService) {
@@ -99,11 +73,14 @@ export const canPerformAccessService = async (
};
export const canPeformDeleteService = async (
- authId: string,
+ userId: string,
serviceId: string,
+ organizationId: string,
) => {
- const { accessedServices, canDeleteServices } =
- await findUserByAuthId(authId);
+ const { accessedServices, canDeleteServices } = await findMemberById(
+ userId,
+ organizationId,
+ );
const haveAccessToService = accessedServices.includes(serviceId);
if (canDeleteServices && haveAccessToService) {
@@ -113,8 +90,11 @@ export const canPeformDeleteService = async (
return false;
};
-export const canPerformCreationProject = async (authId: string) => {
- const { canCreateProjects } = await findUserByAuthId(authId);
+export const canPerformCreationProject = async (
+ userId: string,
+ organizationId: string,
+) => {
+ const { canCreateProjects } = await findMemberById(userId, organizationId);
if (canCreateProjects) {
return true;
@@ -123,8 +103,11 @@ export const canPerformCreationProject = async (authId: string) => {
return false;
};
-export const canPerformDeleteProject = async (authId: string) => {
- const { canDeleteProjects } = await findUserByAuthId(authId);
+export const canPerformDeleteProject = async (
+ userId: string,
+ organizationId: string,
+) => {
+ const { canDeleteProjects } = await findMemberById(userId, organizationId);
if (canDeleteProjects) {
return true;
@@ -134,10 +117,11 @@ export const canPerformDeleteProject = async (authId: string) => {
};
export const canPerformAccessProject = async (
- authId: string,
+ userId: string,
projectId: string,
+ organizationId: string,
) => {
- const { accessedProjects } = await findUserByAuthId(authId);
+ const { accessedProjects } = await findMemberById(userId, organizationId);
const haveAccessToProject = accessedProjects.includes(projectId);
@@ -147,26 +131,45 @@ export const canPerformAccessProject = async (
return false;
};
-export const canAccessToTraefikFiles = async (authId: string) => {
- const { canAccessToTraefikFiles } = await findUserByAuthId(authId);
+export const canAccessToTraefikFiles = async (
+ userId: string,
+ organizationId: string,
+) => {
+ const { canAccessToTraefikFiles } = await findMemberById(
+ userId,
+ organizationId,
+ );
return canAccessToTraefikFiles;
};
export const checkServiceAccess = async (
- authId: string,
+ userId: string,
serviceId: string,
+ organizationId: string,
action = "access" as "access" | "create" | "delete",
) => {
let hasPermission = false;
switch (action) {
case "create":
- hasPermission = await canPerformCreationService(authId, serviceId);
+ hasPermission = await canPerformCreationService(
+ userId,
+ serviceId,
+ organizationId,
+ );
break;
case "access":
- hasPermission = await canPerformAccessService(authId, serviceId);
+ hasPermission = await canPerformAccessService(
+ userId,
+ serviceId,
+ organizationId,
+ );
break;
case "delete":
- hasPermission = await canPeformDeleteService(authId, serviceId);
+ hasPermission = await canPeformDeleteService(
+ userId,
+ serviceId,
+ organizationId,
+ );
break;
default:
hasPermission = false;
@@ -182,6 +185,7 @@ export const checkServiceAccess = async (
export const checkProjectAccess = async (
authId: string,
action: "create" | "delete" | "access",
+ organizationId: string,
projectId?: string,
) => {
let hasPermission = false;
@@ -190,13 +194,14 @@ export const checkProjectAccess = async (
hasPermission = await canPerformAccessProject(
authId,
projectId as string,
+ organizationId,
);
break;
case "create":
- hasPermission = await canPerformCreationProject(authId);
+ hasPermission = await canPerformCreationProject(authId, organizationId);
break;
case "delete":
- hasPermission = await canPerformDeleteProject(authId);
+ hasPermission = await canPerformDeleteProject(authId, organizationId);
break;
default:
hasPermission = false;
@@ -208,3 +213,82 @@ export const checkProjectAccess = async (
});
}
};
+
+export const findMemberById = async (
+ userId: string,
+ organizationId: string,
+) => {
+ const result = await db.query.member.findFirst({
+ where: and(
+ eq(member.userId, userId),
+ eq(member.organizationId, organizationId),
+ ),
+ with: {
+ user: true,
+ },
+ });
+
+ if (!result) {
+ throw new TRPCError({
+ code: "UNAUTHORIZED",
+ message: "Permission denied",
+ });
+ }
+ return result;
+};
+
+export const updateUser = async (userId: string, userData: Partial) => {
+ const user = await db
+ .update(users_temp)
+ .set({
+ ...userData,
+ })
+ .where(eq(users_temp.id, userId))
+ .returning()
+ .then((res) => res[0]);
+
+ return user;
+};
+
+export const createApiKey = async (
+ userId: string,
+ input: {
+ name: string;
+ prefix?: string;
+ expiresIn?: number;
+ metadata: {
+ organizationId: string;
+ };
+ rateLimitEnabled?: boolean;
+ rateLimitTimeWindow?: number;
+ rateLimitMax?: number;
+ remaining?: number;
+ refillAmount?: number;
+ refillInterval?: number;
+ },
+) => {
+ const apiKey = await auth.createApiKey({
+ body: {
+ name: input.name,
+ expiresIn: input.expiresIn,
+ prefix: input.prefix,
+ rateLimitEnabled: input.rateLimitEnabled,
+ rateLimitTimeWindow: input.rateLimitTimeWindow,
+ rateLimitMax: input.rateLimitMax,
+ remaining: input.remaining,
+ refillAmount: input.refillAmount,
+ refillInterval: input.refillInterval,
+ userId,
+ },
+ });
+
+ if (input.metadata) {
+ await db
+ .update(apikey)
+ .set({
+ metadata: JSON.stringify(input.metadata),
+ })
+ .where(eq(apikey.id, apiKey.id));
+ }
+ return apiKey;
+};
diff --git a/packages/server/src/setup/monitoring-setup.ts b/packages/server/src/setup/monitoring-setup.ts
new file mode 100644
index 000000000..75b9a928f
--- /dev/null
+++ b/packages/server/src/setup/monitoring-setup.ts
@@ -0,0 +1,148 @@
+import { findServerById } from "@dokploy/server/services/server";
+import type { ContainerCreateOptions } from "dockerode";
+import { IS_CLOUD } from "../constants";
+import { findUserById } from "../services/admin";
+import { getDokployImageTag } from "../services/settings";
+import { pullImage, pullRemoteImage } from "../utils/docker/utils";
+import { execAsync, execAsyncRemote } from "../utils/process/execAsync";
+import { getRemoteDocker } from "../utils/servers/remote-docker";
+
+export const setupMonitoring = async (serverId: string) => {
+ const server = await findServerById(serverId);
+
+ const containerName = "dokploy-monitoring";
+ let imageName = "dokploy/monitoring:latest";
+
+ if (
+ (getDokployImageTag() !== "latest" ||
+ process.env.NODE_ENV === "development") &&
+ !IS_CLOUD
+ ) {
+ imageName = "dokploy/monitoring:canary";
+ }
+
+ const settings: ContainerCreateOptions = {
+ name: containerName,
+ Env: [`METRICS_CONFIG=${JSON.stringify(server?.metricsConfig)}`],
+ Image: imageName,
+ HostConfig: {
+ // Memory: 100 * 1024 * 1024, // 100MB en bytes
+ // PidMode: "host",
+ // CapAdd: ["NET_ADMIN", "SYS_ADMIN"],
+ // Privileged: true,
+ PortBindings: {
+ [`${server.metricsConfig.server.port}/tcp`]: [
+ {
+ HostPort: server.metricsConfig.server.port.toString(),
+ },
+ ],
+ },
+ Binds: [
+ "/var/run/docker.sock:/var/run/docker.sock:ro",
+ "/sys:/host/sys:ro",
+ "/etc/os-release:/etc/os-release:ro",
+ "/proc:/host/proc:ro",
+ "/etc/dokploy/monitoring/monitoring.db:/app/monitoring.db",
+ ],
+ NetworkMode: "host",
+ },
+ ExposedPorts: {
+ [`${server.metricsConfig.server.port}/tcp`]: {},
+ },
+ };
+ const docker = await getRemoteDocker(serverId);
+ try {
+ await execAsyncRemote(
+ serverId,
+ "mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
+ );
+ if (serverId) {
+ await pullRemoteImage(imageName, serverId);
+ }
+
+ // Check if container exists
+ const container = docker.getContainer(containerName);
+ try {
+ await container.inspect();
+ await container.remove({ force: true });
+ console.log("Removed existing container");
+ } catch (_error) {
+ // Container doesn't exist, continue
+ }
+
+ await docker.createContainer(settings);
+ const newContainer = docker.getContainer(containerName);
+ await newContainer.start();
+
+ console.log("Monitoring Started ");
+ } catch (error) {
+ console.log("Monitoring Not Found: Starting ", error);
+ }
+};
+
+export const setupWebMonitoring = async (userId: string) => {
+ const user = await findUserById(userId);
+
+ const containerName = "dokploy-monitoring";
+ let imageName = "dokploy/monitoring:latest";
+
+ if (
+ (getDokployImageTag() !== "latest" ||
+ process.env.NODE_ENV === "development") &&
+ !IS_CLOUD
+ ) {
+ imageName = "dokploy/monitoring:canary";
+ }
+
+ const settings: ContainerCreateOptions = {
+ name: containerName,
+ Env: [`METRICS_CONFIG=${JSON.stringify(user?.metricsConfig)}`],
+ Image: imageName,
+ HostConfig: {
+ // Memory: 100 * 1024 * 1024, // 100MB en bytes
+ // PidMode: "host",
+ // CapAdd: ["NET_ADMIN", "SYS_ADMIN"],
+ // Privileged: true,
+ PortBindings: {
+ [`${user?.metricsConfig?.server?.port}/tcp`]: [
+ {
+ HostPort: user?.metricsConfig?.server?.port.toString(),
+ },
+ ],
+ },
+ Binds: [
+ "/var/run/docker.sock:/var/run/docker.sock:ro",
+ "/sys:/host/sys:ro",
+ "/etc/os-release:/etc/os-release:ro",
+ "/proc:/host/proc:ro",
+ "/etc/dokploy/monitoring/monitoring.db:/app/monitoring.db",
+ ],
+ // NetworkMode: "host",
+ },
+ ExposedPorts: {
+ [`${user?.metricsConfig?.server?.port}/tcp`]: {},
+ },
+ };
+ const docker = await getRemoteDocker();
+ try {
+ await execAsync(
+ "mkdir -p /etc/dokploy/monitoring && touch /etc/dokploy/monitoring/monitoring.db",
+ );
+ await pullImage(imageName);
+
+ const container = docker.getContainer(containerName);
+ try {
+ await container.inspect();
+ await container.remove({ force: true });
+ console.log("Removed existing container");
+ } catch (_error) {}
+
+ await docker.createContainer(settings);
+ const newContainer = docker.getContainer(containerName);
+ await newContainer.start();
+
+ console.log("Monitoring Started ");
+ } catch (error) {
+ console.log("Monitoring Not Found: Starting ", error);
+ }
+};
diff --git a/packages/server/src/setup/postgres-setup.ts b/packages/server/src/setup/postgres-setup.ts
index b5794c2b2..cf162f1ed 100644
--- a/packages/server/src/setup/postgres-setup.ts
+++ b/packages/server/src/setup/postgres-setup.ts
@@ -54,10 +54,16 @@ export const initializePostgres = async () => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
-
console.log("Postgres Started ✅");
- } catch (error) {
- await docker.createService(settings);
+ } catch (_) {
+ try {
+ await docker.createService(settings);
+ } catch (error: any) {
+ if (error?.statusCode !== 409) {
+ throw error;
+ }
+ console.log("Postgres service already exists, continuing...");
+ }
console.log("Postgres Not Found: Starting ✅");
}
};
diff --git a/packages/server/src/setup/redis-setup.ts b/packages/server/src/setup/redis-setup.ts
index 1c3b545a5..7366546da 100644
--- a/packages/server/src/setup/redis-setup.ts
+++ b/packages/server/src/setup/redis-setup.ts
@@ -52,8 +52,15 @@ export const initializeRedis = async () => {
...settings,
});
console.log("Redis Started ✅");
- } catch (error) {
- await docker.createService(settings);
+ } catch (_) {
+ try {
+ await docker.createService(settings);
+ } catch (error: any) {
+ if (error?.statusCode !== 409) {
+ throw error;
+ }
+ console.log("Redis service already exists, continuing...");
+ }
console.log("Redis Not Found: Starting ✅");
}
};
diff --git a/packages/server/src/setup/server-audit.ts b/packages/server/src/setup/server-audit.ts
index df00e9a74..b9283c313 100644
--- a/packages/server/src/setup/server-audit.ts
+++ b/packages/server/src/setup/server-audit.ts
@@ -89,7 +89,7 @@ export const serverAudit = async (serverId: string) => {
.on("data", (data: string) => {
output += data;
})
- .stderr.on("data", (data) => {});
+ .stderr.on("data", (_data) => {});
});
})
.on("error", (err) => {
diff --git a/packages/server/src/setup/server-validate.ts b/packages/server/src/setup/server-validate.ts
index 4ca21df85..c86206b61 100644
--- a/packages/server/src/setup/server-validate.ts
+++ b/packages/server/src/setup/server-validate.ts
@@ -128,7 +128,7 @@ export const serverValidate = async (serverId: string) => {
.on("data", (data: string) => {
output += data;
})
- .stderr.on("data", (data) => {});
+ .stderr.on("data", (_data) => {});
});
})
.on("error", (err) => {
diff --git a/packages/server/src/setup/setup.ts b/packages/server/src/setup/setup.ts
index c59877022..eeef32dd2 100644
--- a/packages/server/src/setup/setup.ts
+++ b/packages/server/src/setup/setup.ts
@@ -18,7 +18,7 @@ export const dockerSwarmInitialized = async () => {
await docker.swarmInspect();
return true;
- } catch (e) {
+ } catch (_e) {
return false;
}
};
@@ -41,7 +41,7 @@ export const dockerNetworkInitialized = async () => {
try {
await docker.getNetwork("dokploy-network").inspect();
return true;
- } catch (e) {
+ } catch (_e) {
return false;
}
};
diff --git a/packages/server/src/setup/traefik-setup.ts b/packages/server/src/setup/traefik-setup.ts
index 270a4447a..e8d019424 100644
--- a/packages/server/src/setup/traefik-setup.ts
+++ b/packages/server/src/setup/traefik-setup.ts
@@ -68,9 +68,6 @@ export const initializeTraefik = async ({
Replicas: 1,
},
},
- Labels: {
- "traefik.enable": "true",
- },
EndpointSpec: {
Ports: [
{
@@ -130,8 +127,15 @@ export const initializeTraefik = async ({
});
console.log("Traefik Started ✅");
- } catch (error) {
- await docker.createService(settings);
+ } catch (_) {
+ try {
+ await docker.createService(settings);
+ } catch (error: any) {
+ if (error?.statusCode !== 409) {
+ throw error;
+ }
+ console.log("Traefik service already exists, continuing...");
+ }
console.log("Traefik Not Found: Starting ✅");
}
};
@@ -189,10 +193,12 @@ export const getDefaultTraefikConfig = () => {
: {
swarm: {
exposedByDefault: false,
- watch: false,
+ watch: true,
},
docker: {
exposedByDefault: false,
+ watch: true,
+ network: "dokploy-network",
},
}),
file: {
@@ -243,10 +249,12 @@ export const getDefaultServerTraefikConfig = () => {
providers: {
swarm: {
exposedByDefault: false,
- watch: false,
+ watch: true,
},
docker: {
exposedByDefault: false,
+ watch: true,
+ network: "dokploy-network",
},
file: {
directory: "/etc/dokploy/traefik/dynamic",
diff --git a/packages/server/src/types/with.ts b/packages/server/src/types/with.ts
index c4826f734..467020a21 100644
--- a/packages/server/src/types/with.ts
+++ b/packages/server/src/types/with.ts
@@ -36,7 +36,7 @@ type AnyObj = Record;
type ZodObj = {
[key in keyof T]: z.ZodType;
};
-const zObject = (arg: ZodObj) => z.object(arg);
+const _zObject = (arg: ZodObj) => z.object(arg);
// const goodDogScheme = zObject({
// // prueba: schema.selectDatabaseSchema,
diff --git a/packages/server/src/utils/access-log/handler.ts b/packages/server/src/utils/access-log/handler.ts
index b1fd925c6..69d0cc68b 100644
--- a/packages/server/src/utils/access-log/handler.ts
+++ b/packages/server/src/utils/access-log/handler.ts
@@ -1,8 +1,8 @@
import { IS_CLOUD, paths } from "@dokploy/server/constants";
-import { updateAdmin } from "@dokploy/server/services/admin";
import { type RotatingFileStream, createStream } from "rotating-file-stream";
-import { db } from "../../db";
import { execAsync } from "../process/execAsync";
+import { findAdmin } from "@dokploy/server/services/admin";
+import { updateUser } from "@dokploy/server/services/user";
class LogRotationManager {
private static instance: LogRotationManager;
@@ -30,17 +30,16 @@ class LogRotationManager {
}
private async getStateFromDB(): Promise {
- const setting = await db.query.admins.findFirst({});
- return setting?.enableLogRotation ?? false;
+ const admin = await findAdmin();
+ return admin?.user.enableLogRotation ?? false;
}
private async setStateInDB(active: boolean): Promise {
- const admin = await db.query.admins.findFirst({});
-
+ const admin = await findAdmin();
if (!admin) {
return;
}
- await updateAdmin(admin?.authId, {
+ await updateUser(admin.user.id, {
enableLogRotation: active,
});
}
diff --git a/packages/server/src/utils/backups/index.ts b/packages/server/src/utils/backups/index.ts
index 922232a0c..7699a42e4 100644
--- a/packages/server/src/utils/backups/index.ts
+++ b/packages/server/src/utils/backups/index.ts
@@ -1,4 +1,3 @@
-import { findAdmin } from "@dokploy/server/services/admin";
import { getAllServers } from "@dokploy/server/services/server";
import { scheduleJob } from "node-schedule";
import { db } from "../../db/index";
@@ -12,13 +11,14 @@ import { runMariadbBackup } from "./mariadb";
import { runMongoBackup } from "./mongo";
import { runMySqlBackup } from "./mysql";
import { runPostgresBackup } from "./postgres";
+import { findAdmin } from "../../services/admin";
export const initCronJobs = async () => {
console.log("Setting up cron jobs....");
const admin = await findAdmin();
- if (admin?.enableDockerCleanup) {
+ if (admin?.user.enableDockerCleanup) {
scheduleJob("docker-cleanup", "0 0 * * *", async () => {
console.log(
`Docker Cleanup ${new Date().toLocaleString()}] Running docker cleanup`,
@@ -26,7 +26,7 @@ export const initCronJobs = async () => {
await cleanUpUnusedImages();
await cleanUpDockerBuilder();
await cleanUpSystemPrune();
- await sendDockerCleanupNotifications(admin.adminId);
+ await sendDockerCleanupNotifications(admin.user.id);
});
}
@@ -43,7 +43,7 @@ export const initCronJobs = async () => {
await cleanUpDockerBuilder(serverId);
await cleanUpSystemPrune(serverId);
await sendDockerCleanupNotifications(
- admin.adminId,
+ admin.user.id,
`Docker cleanup for Server ${name} (${serverId})`,
);
});
diff --git a/packages/server/src/utils/backups/mariadb.ts b/packages/server/src/utils/backups/mariadb.ts
index 79cba9c57..56c2919c4 100644
--- a/packages/server/src/utils/backups/mariadb.ts
+++ b/packages/server/src/utils/backups/mariadb.ts
@@ -49,7 +49,7 @@ export const runMariadbBackup = async (
projectName: project.name,
databaseType: "mariadb",
type: "success",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
} catch (error) {
console.log(error);
@@ -60,7 +60,7 @@ export const runMariadbBackup = async (
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
throw error;
}
diff --git a/packages/server/src/utils/backups/mongo.ts b/packages/server/src/utils/backups/mongo.ts
index ddd1b8896..a40ec4f47 100644
--- a/packages/server/src/utils/backups/mongo.ts
+++ b/packages/server/src/utils/backups/mongo.ts
@@ -46,7 +46,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
projectName: project.name,
databaseType: "mongodb",
type: "success",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
} catch (error) {
console.log(error);
@@ -57,7 +57,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
throw error;
}
diff --git a/packages/server/src/utils/backups/mysql.ts b/packages/server/src/utils/backups/mysql.ts
index b505204c2..1272fc3ed 100644
--- a/packages/server/src/utils/backups/mysql.ts
+++ b/packages/server/src/utils/backups/mysql.ts
@@ -1,4 +1,3 @@
-import { unlink } from "node:fs/promises";
import path from "node:path";
import type { BackupSchedule } from "@dokploy/server/services/backup";
import type { MySql } from "@dokploy/server/services/mysql";
@@ -46,7 +45,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
projectName: project.name,
databaseType: "mysql",
type: "success",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
} catch (error) {
console.log(error);
@@ -57,7 +56,7 @@ export const runMySqlBackup = async (mysql: MySql, backup: BackupSchedule) => {
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
throw error;
}
diff --git a/packages/server/src/utils/backups/postgres.ts b/packages/server/src/utils/backups/postgres.ts
index e9609fc89..5ada2aa9d 100644
--- a/packages/server/src/utils/backups/postgres.ts
+++ b/packages/server/src/utils/backups/postgres.ts
@@ -49,7 +49,7 @@ export const runPostgresBackup = async (
projectName: project.name,
databaseType: "postgres",
type: "success",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
} catch (error) {
await sendDatabaseBackupNotifications({
@@ -59,7 +59,7 @@ export const runPostgresBackup = async (
type: "error",
// @ts-ignore
errorMessage: error?.message || "Error message not provided",
- adminId: project.adminId,
+ organizationId: project.organizationId,
});
throw error;
diff --git a/packages/server/src/utils/backups/utils.ts b/packages/server/src/utils/backups/utils.ts
index 0d78ff961..c76f79626 100644
--- a/packages/server/src/utils/backups/utils.ts
+++ b/packages/server/src/utils/backups/utils.ts
@@ -28,7 +28,7 @@ export const removeScheduleBackup = (backupId: string) => {
};
export const getS3Credentials = (destination: Destination) => {
- const { accessKey, secretAccessKey, bucket, region, endpoint, provider } =
+ const { accessKey, secretAccessKey, region, endpoint, provider } =
destination;
const rcloneFlags = [
`--s3-access-key-id=${accessKey}`,
diff --git a/packages/server/src/utils/builders/compose.ts b/packages/server/src/utils/builders/compose.ts
index ae181fc35..19e7d1529 100644
--- a/packages/server/src/utils/builders/compose.ts
+++ b/packages/server/src/utils/builders/compose.ts
@@ -12,8 +12,12 @@ import {
writeDomainsToCompose,
writeDomainsToComposeRemote,
} from "../docker/domain";
-import { encodeBase64, prepareEnvironmentVariables } from "../docker/utils";
-import { execAsyncRemote } from "../process/execAsync";
+import {
+ encodeBase64,
+ getEnviromentVariablesObject,
+ prepareEnvironmentVariables,
+} from "../docker/utils";
+import { execAsync, execAsyncRemote } from "../process/execAsync";
import { spawnAsync } from "../process/spawnAsync";
export type ComposeNested = InferResultType<
@@ -29,13 +33,19 @@ export const buildCompose = async (compose: ComposeNested, logPath: string) => {
await writeDomainsToCompose(compose, domains);
createEnvFile(compose);
+ if (compose.isolatedDeployment) {
+ await execAsync(
+ `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create --attachable ${compose.appName}`,
+ );
+ }
+
const logContent = `
-App Name: ${appName}
-Build Compose 🐳
-Detected: ${mounts.length} mounts 📂
-Command: docker ${command}
-Source Type: docker ${sourceType} ✅
-Compose Type: ${composeType} ✅`;
+ App Name: ${appName}
+ Build Compose 🐳
+ Detected: ${mounts.length} mounts 📂
+ Command: docker ${command}
+ Source Type: docker ${sourceType} ✅
+ Compose Type: ${composeType} ✅`;
const logBox = boxen(logContent, {
padding: {
left: 1,
@@ -46,7 +56,6 @@ Compose Type: ${composeType} ✅`;
borderStyle: "double",
});
writeStream.write(`\n${logBox}\n`);
-
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
await spawnAsync(
@@ -62,10 +71,19 @@ Compose Type: ${composeType} ✅`;
env: {
NODE_ENV: process.env.NODE_ENV,
PATH: process.env.PATH,
+ ...(composeType === "stack" && {
+ ...getEnviromentVariablesObject(compose.env, compose.project.env),
+ }),
},
},
);
+ if (compose.isolatedDeployment) {
+ await execAsync(
+ `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1`,
+ ).catch(() => {});
+ }
+
writeStream.write("Docker Compose Deployed: ✅");
} catch (error) {
writeStream.write(`Error ❌ ${(error as Error).message}`);
@@ -80,11 +98,11 @@ export const getBuildComposeCommand = async (
logPath: string,
) => {
const { COMPOSE_PATH } = paths(true);
- const { sourceType, appName, mounts, composeType, domains, composePath } =
- compose;
+ const { sourceType, appName, mounts, composeType, domains } = compose;
const command = createCommand(compose);
const envCommand = getCreateEnvFileCommand(compose);
const projectPath = join(COMPOSE_PATH, compose.appName, "code");
+ const exportEnvCommand = getExportEnvCommand(compose);
const newCompose = await writeDomainsToComposeRemote(
compose,
@@ -120,7 +138,10 @@ Compose Type: ${composeType} ✅`;
cd "${projectPath}";
+ ${exportEnvCommand}
+ ${compose.isolatedDeployment ? `docker network inspect ${compose.appName} >/dev/null 2>&1 || docker network create --attachable ${compose.appName}` : ""}
docker ${command.split(" ").join(" ")} >> "${logPath}" 2>&1 || { echo "Error: ❌ Docker command failed" >> "${logPath}"; exit 1; }
+ ${compose.isolatedDeployment ? `docker network connect ${compose.appName} $(docker ps --filter "name=dokploy-traefik" -q) >/dev/null 2>&1` : ""}
echo "Docker Compose Deployed: ✅" >> "${logPath}"
} || {
@@ -144,7 +165,6 @@ const sanitizeCommand = (command: string) => {
export const createCommand = (compose: ComposeNested) => {
const { composeType, appName, sourceType } = compose;
-
if (compose.command) {
return `${sanitizeCommand(compose.command)}`;
}
@@ -219,3 +239,17 @@ touch ${envFilePath};
echo "${encodedContent}" | base64 -d > "${envFilePath}";
`;
};
+
+const getExportEnvCommand = (compose: ComposeNested) => {
+ if (compose.composeType !== "stack") return "";
+
+ const envVars = getEnviromentVariablesObject(
+ compose.env,
+ compose.project.env,
+ );
+ const exports = Object.entries(envVars)
+ .map(([key, value]) => `export ${key}=${JSON.stringify(value)}`)
+ .join("\n");
+
+ return exports ? `\n# Export environment variables\n${exports}\n` : "";
+};
diff --git a/packages/server/src/utils/builders/index.ts b/packages/server/src/utils/builders/index.ts
index d67482755..d777b1a36 100644
--- a/packages/server/src/utils/builders/index.ts
+++ b/packages/server/src/utils/builders/index.ts
@@ -197,7 +197,7 @@ export const mechanizeDockerContainer = async (
ForceUpdate: inspect.Spec.TaskTemplate.ForceUpdate + 1,
},
});
- } catch (error) {
+ } catch (_error) {
await docker.createService(settings);
}
};
diff --git a/packages/server/src/utils/builders/nixpacks.ts b/packages/server/src/utils/builders/nixpacks.ts
index 56560e4e2..c13f82a56 100644
--- a/packages/server/src/utils/builders/nixpacks.ts
+++ b/packages/server/src/utils/builders/nixpacks.ts
@@ -91,7 +91,7 @@ export const getNixpacksCommand = (
application: ApplicationNested,
logPath: string,
) => {
- const { env, appName, publishDirectory, serverId } = application;
+ const { env, appName, publishDirectory } = application;
const buildAppDirectory = getBuildAppDirectory(application);
const buildContainerId = `${appName}-${nanoid(10)}`;
diff --git a/packages/server/src/utils/databases/mariadb.ts b/packages/server/src/utils/databases/mariadb.ts
index d1b41fc33..ead5a618a 100644
--- a/packages/server/src/utils/databases/mariadb.ts
+++ b/packages/server/src/utils/databases/mariadb.ts
@@ -98,7 +98,7 @@ export const buildMariadb = async (mariadb: MariadbNested) => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
- } catch (error) {
+ } catch (_error) {
await docker.createService(settings);
}
};
diff --git a/packages/server/src/utils/databases/mongo.ts b/packages/server/src/utils/databases/mongo.ts
index 5af58eef7..ace9c9721 100644
--- a/packages/server/src/utils/databases/mongo.ts
+++ b/packages/server/src/utils/databases/mongo.ts
@@ -152,7 +152,7 @@ ${command ?? "wait $MONGOD_PID"}`;
version: Number.parseInt(inspect.Version.Index),
...settings,
});
- } catch (error) {
+ } catch (_error) {
await docker.createService(settings);
}
};
diff --git a/packages/server/src/utils/databases/mysql.ts b/packages/server/src/utils/databases/mysql.ts
index 5a6911771..de28cfe6b 100644
--- a/packages/server/src/utils/databases/mysql.ts
+++ b/packages/server/src/utils/databases/mysql.ts
@@ -104,7 +104,7 @@ export const buildMysql = async (mysql: MysqlNested) => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
- } catch (error) {
+ } catch (_error) {
await docker.createService(settings);
}
};
diff --git a/packages/server/src/utils/databases/redis.ts b/packages/server/src/utils/databases/redis.ts
index 724069a17..aef862802 100644
--- a/packages/server/src/utils/databases/redis.ts
+++ b/packages/server/src/utils/databases/redis.ts
@@ -95,7 +95,7 @@ export const buildRedis = async (redis: RedisNested) => {
version: Number.parseInt(inspect.Version.Index),
...settings,
});
- } catch (error) {
+ } catch (_error) {
await docker.createService(settings);
}
};
diff --git a/packages/server/src/utils/docker/collision.ts b/packages/server/src/utils/docker/collision.ts
new file mode 100644
index 000000000..d3f131c67
--- /dev/null
+++ b/packages/server/src/utils/docker/collision.ts
@@ -0,0 +1,46 @@
+import { findComposeById } from "@dokploy/server/services/compose";
+import { dump, load } from "js-yaml";
+import { addAppNameToAllServiceNames } from "./collision/root-network";
+import { generateRandomHash } from "./compose";
+import { addSuffixToAllVolumes } from "./compose/volume";
+import type { ComposeSpecification } from "./types";
+
+export const addAppNameToPreventCollision = (
+ composeData: ComposeSpecification,
+ appName: string,
+): ComposeSpecification => {
+ let updatedComposeData = { ...composeData };
+
+ updatedComposeData = addAppNameToAllServiceNames(updatedComposeData, appName);
+ updatedComposeData = addSuffixToAllVolumes(updatedComposeData, appName);
+ return updatedComposeData;
+};
+
+export const randomizeIsolatedDeploymentComposeFile = async (
+ composeId: string,
+ suffix?: string,
+) => {
+ const compose = await findComposeById(composeId);
+ const composeFile = compose.composeFile;
+ const composeData = load(composeFile) as ComposeSpecification;
+
+ const randomSuffix = suffix || compose.appName || generateRandomHash();
+
+ const newComposeFile = addAppNameToPreventCollision(
+ composeData,
+ randomSuffix,
+ );
+
+ return dump(newComposeFile);
+};
+
+export const randomizeDeployableSpecificationFile = (
+ composeSpec: ComposeSpecification,
+ suffix?: string,
+) => {
+ if (!suffix) {
+ return composeSpec;
+ }
+ const newComposeFile = addAppNameToPreventCollision(composeSpec, suffix);
+ return newComposeFile;
+};
diff --git a/packages/server/src/utils/docker/collision/root-network.ts b/packages/server/src/utils/docker/collision/root-network.ts
new file mode 100644
index 000000000..ed36d4bd6
--- /dev/null
+++ b/packages/server/src/utils/docker/collision/root-network.ts
@@ -0,0 +1,62 @@
+import _ from "lodash";
+import type { ComposeSpecification, DefinitionsService } from "../types";
+
+export const addAppNameToRootNetwork = (
+ composeData: ComposeSpecification,
+ appName: string,
+): ComposeSpecification => {
+ const updatedComposeData = { ...composeData };
+
+ // Initialize networks if it doesn't exist
+ if (!updatedComposeData.networks) {
+ updatedComposeData.networks = {};
+ }
+
+ // Add the new network with the app name
+ updatedComposeData.networks[appName] = {
+ name: appName,
+ external: true,
+ };
+
+ return updatedComposeData;
+};
+
+export const addAppNameToServiceNetworks = (
+ services: { [key: string]: DefinitionsService },
+ appName: string,
+): { [key: string]: DefinitionsService } => {
+ return _.mapValues(services, (service) => {
+ if (!service.networks) {
+ service.networks = [appName];
+ return service;
+ }
+
+ if (Array.isArray(service.networks)) {
+ if (!service.networks.includes(appName)) {
+ service.networks.push(appName);
+ }
+ } else {
+ service.networks[appName] = {};
+ }
+
+ return service;
+ });
+};
+
+export const addAppNameToAllServiceNames = (
+ composeData: ComposeSpecification,
+ appName: string,
+): ComposeSpecification => {
+ let updatedComposeData = { ...composeData };
+
+ updatedComposeData = addAppNameToRootNetwork(updatedComposeData, appName);
+
+ if (updatedComposeData.services) {
+ updatedComposeData.services = addAppNameToServiceNetworks(
+ updatedComposeData.services,
+ appName,
+ );
+ }
+
+ return updatedComposeData;
+};
diff --git a/packages/server/src/utils/docker/domain.ts b/packages/server/src/utils/docker/domain.ts
index 69bfa222a..c4ced3f44 100644
--- a/packages/server/src/utils/docker/domain.ts
+++ b/packages/server/src/utils/docker/domain.ts
@@ -26,6 +26,7 @@ import {
createComposeFileRaw,
createComposeFileRawRemote,
} from "../providers/raw";
+import { randomizeDeployableSpecificationFile } from "./collision";
import { randomizeSpecificationFile } from "./compose";
import type {
ComposeSpecification,
@@ -108,7 +109,7 @@ export const loadDockerComposeRemote = async (
if (!stdout) return null;
const parsedConfig = load(stdout) as ComposeSpecification;
return parsedConfig;
- } catch (err) {
+ } catch (_err) {
return null;
}
};
@@ -190,7 +191,13 @@ export const addDomainToCompose = async (
return null;
}
- if (compose.randomize) {
+ if (compose.isolatedDeployment) {
+ const randomized = randomizeDeployableSpecificationFile(
+ result,
+ compose.suffix || compose.appName,
+ );
+ result = randomized;
+ } else if (compose.randomize) {
const randomized = randomizeSpecificationFile(result, compose.suffix);
result = randomized;
}
@@ -203,9 +210,6 @@ export const addDomainToCompose = async (
if (!result?.services?.[serviceName]) {
throw new Error(`The service ${serviceName} not found in the compose`);
}
- if (!result.services[serviceName].labels) {
- result.services[serviceName].labels = [];
- }
const httpLabels = await createDomainLabels(appName, domain, "web");
if (https) {
@@ -217,7 +221,24 @@ export const addDomainToCompose = async (
httpLabels.push(...httpsLabels);
}
- const labels = result.services[serviceName].labels;
+ let labels: DefinitionsService["labels"] = [];
+ if (compose.composeType === "docker-compose") {
+ if (!result.services[serviceName].labels) {
+ result.services[serviceName].labels = [];
+ }
+
+ labels = result.services[serviceName].labels;
+ } else {
+ // Stack Case
+ if (!result.services[serviceName].deploy) {
+ result.services[serviceName].deploy = {};
+ }
+ if (!result.services[serviceName].deploy.labels) {
+ result.services[serviceName].deploy.labels = [];
+ }
+
+ labels = result.services[serviceName].deploy.labels;
+ }
if (Array.isArray(labels)) {
if (!labels.includes("traefik.enable=true")) {
@@ -226,14 +247,18 @@ export const addDomainToCompose = async (
labels.push(...httpLabels);
}
- // Add the dokploy-network to the service
- result.services[serviceName].networks = addDokployNetworkToService(
- result.services[serviceName].networks,
- );
+ if (!compose.isolatedDeployment) {
+ // Add the dokploy-network to the service
+ result.services[serviceName].networks = addDokployNetworkToService(
+ result.services[serviceName].networks,
+ );
+ }
}
// Add dokploy-network to the root of the compose file
- result.networks = addDokployNetworkToRoot(result.networks);
+ if (!compose.isolatedDeployment) {
+ result.networks = addDokployNetworkToRoot(result.networks);
+ }
return result;
};
diff --git a/packages/server/src/utils/docker/utils.ts b/packages/server/src/utils/docker/utils.ts
index 654d83301..71b7e4aac 100644
--- a/packages/server/src/utils/docker/utils.ts
+++ b/packages/server/src/utils/docker/utils.ts
@@ -100,7 +100,7 @@ export const containerExists = async (containerName: string) => {
try {
await container.inspect();
return true;
- } catch (error) {
+ } catch (_error) {
return false;
}
};
@@ -144,10 +144,11 @@ export const getContainerByName = (name: string): Promise => {
};
export const cleanUpUnusedImages = async (serverId?: string) => {
try {
+ const command = "docker image prune --force";
if (serverId) {
- await execAsyncRemote(serverId, "docker image prune --all --force");
+ await execAsyncRemote(serverId, command);
} else {
- await execAsync("docker image prune --all --force");
+ await execAsync(command);
}
} catch (error) {
console.error(error);
@@ -157,10 +158,11 @@ export const cleanUpUnusedImages = async (serverId?: string) => {
export const cleanStoppedContainers = async (serverId?: string) => {
try {
+ const command = "docker container prune --force";
if (serverId) {
- await execAsyncRemote(serverId, "docker container prune --force");
+ await execAsyncRemote(serverId, command);
} else {
- await execAsync("docker container prune --force");
+ await execAsync(command);
}
} catch (error) {
console.error(error);
@@ -170,10 +172,11 @@ export const cleanStoppedContainers = async (serverId?: string) => {
export const cleanUpUnusedVolumes = async (serverId?: string) => {
try {
+ const command = "docker volume prune --force";
if (serverId) {
- await execAsyncRemote(serverId, "docker volume prune --all --force");
+ await execAsyncRemote(serverId, command);
} else {
- await execAsync("docker volume prune --all --force");
+ await execAsync(command);
}
} catch (error) {
console.error(error);
@@ -199,21 +202,20 @@ export const cleanUpInactiveContainers = async () => {
};
export const cleanUpDockerBuilder = async (serverId?: string) => {
+ const command = "docker builder prune --all --force";
if (serverId) {
- await execAsyncRemote(serverId, "docker builder prune --all --force");
+ await execAsyncRemote(serverId, command);
} else {
- await execAsync("docker builder prune --all --force");
+ await execAsync(command);
}
};
export const cleanUpSystemPrune = async (serverId?: string) => {
+ const command = "docker system prune --all --force --volumes";
if (serverId) {
- await execAsyncRemote(
- serverId,
- "docker system prune --all --force --volumes",
- );
+ await execAsyncRemote(serverId, command);
} else {
- await execAsync("docker system prune --all --force --volumes");
+ await execAsync(command);
}
};
@@ -238,7 +240,7 @@ export const startServiceRemote = async (serverId: string, appName: string) => {
export const removeService = async (
appName: string,
serverId?: string | null,
- deleteVolumes = false,
+ _deleteVolumes = false,
) => {
try {
const command = `docker service rm ${appName}`;
@@ -276,12 +278,15 @@ export const prepareEnvironmentVariables = (
return resolvedVars;
};
-export const prepareBuildArgs = (input: string | null) => {
- const pairs = (input ?? "").split("\n");
+export const getEnviromentVariablesObject = (
+ input: string | null,
+ projectEnv?: string | null,
+) => {
+ const envs = prepareEnvironmentVariables(input, projectEnv);
const jsonObject: Record = {};
- for (const pair of pairs) {
+ for (const pair of envs) {
const [key, value] = pair.split("=");
if (key && value) {
jsonObject[key] = value;
diff --git a/packages/server/src/utils/gpu-setup.ts b/packages/server/src/utils/gpu-setup.ts
index 6a6611b4a..a815a00cc 100644
--- a/packages/server/src/utils/gpu-setup.ts
+++ b/packages/server/src/utils/gpu-setup.ts
@@ -34,7 +34,7 @@ export async function checkGPUStatus(serverId?: string): Promise {
...gpuInfo,
...cudaInfo,
};
- } catch (error) {
+ } catch (_error) {
return {
driverInstalled: false,
driverVersion: undefined,
@@ -315,7 +315,7 @@ const setupLocalServer = async (daemonConfig: any) => {
try {
await execAsync(setupCommands);
- } catch (error) {
+ } catch (_error) {
throw new Error(
"Failed to configure GPU support. Please ensure you have sudo privileges and try again.",
);
diff --git a/packages/server/src/utils/notifications/build-error.ts b/packages/server/src/utils/notifications/build-error.ts
index 95936652c..c873c9ab5 100644
--- a/packages/server/src/utils/notifications/build-error.ts
+++ b/packages/server/src/utils/notifications/build-error.ts
@@ -18,7 +18,7 @@ interface Props {
applicationType: string;
errorMessage: string;
buildLink: string;
- adminId: string;
+ organizationId: string;
}
export const sendBuildErrorNotifications = async ({
@@ -27,14 +27,14 @@ export const sendBuildErrorNotifications = async ({
applicationType,
errorMessage,
buildLink,
- adminId,
+ organizationId,
}: Props) => {
const date = new Date();
const unixDate = ~~(Number(date) / 1000);
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.appBuildError, true),
- eq(notifications.adminId, adminId),
+ eq(notifications.organizationId, organizationId),
),
with: {
email: true,
diff --git a/packages/server/src/utils/notifications/build-success.ts b/packages/server/src/utils/notifications/build-success.ts
index 960f7a6a4..ac470c49f 100644
--- a/packages/server/src/utils/notifications/build-success.ts
+++ b/packages/server/src/utils/notifications/build-success.ts
@@ -18,7 +18,7 @@ interface Props {
applicationName: string;
applicationType: string;
buildLink: string;
- adminId: string;
+ organizationId: string;
domains: Domain[];
}
@@ -27,7 +27,7 @@ export const sendBuildSuccessNotifications = async ({
applicationName,
applicationType,
buildLink,
- adminId,
+ organizationId,
domains,
}: Props) => {
const date = new Date();
@@ -35,7 +35,7 @@ export const sendBuildSuccessNotifications = async ({
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.appDeploy, true),
- eq(notifications.adminId, adminId),
+ eq(notifications.organizationId, organizationId),
),
with: {
email: true,
diff --git a/packages/server/src/utils/notifications/database-backup.ts b/packages/server/src/utils/notifications/database-backup.ts
index 0b1d61f7e..37a4a1ff2 100644
--- a/packages/server/src/utils/notifications/database-backup.ts
+++ b/packages/server/src/utils/notifications/database-backup.ts
@@ -1,4 +1,3 @@
-import { error } from "node:console";
import { db } from "@dokploy/server/db";
import { notifications } from "@dokploy/server/db/schema";
import DatabaseBackupEmail from "@dokploy/server/emails/emails/database-backup";
@@ -19,13 +18,13 @@ export const sendDatabaseBackupNotifications = async ({
databaseType,
type,
errorMessage,
- adminId,
+ organizationId,
}: {
projectName: string;
applicationName: string;
databaseType: "postgres" | "mysql" | "mongodb" | "mariadb";
type: "error" | "success";
- adminId: string;
+ organizationId: string;
errorMessage?: string;
}) => {
const date = new Date();
@@ -33,7 +32,7 @@ export const sendDatabaseBackupNotifications = async ({
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.databaseBackup, true),
- eq(notifications.adminId, adminId),
+ eq(notifications.organizationId, organizationId),
),
with: {
email: true,
diff --git a/packages/server/src/utils/notifications/docker-cleanup.ts b/packages/server/src/utils/notifications/docker-cleanup.ts
index b60e3b0ac..b3959cccd 100644
--- a/packages/server/src/utils/notifications/docker-cleanup.ts
+++ b/packages/server/src/utils/notifications/docker-cleanup.ts
@@ -13,7 +13,7 @@ import {
} from "./utils";
export const sendDockerCleanupNotifications = async (
- adminId: string,
+ organizationId: string,
message = "Docker cleanup for dokploy",
) => {
const date = new Date();
@@ -21,7 +21,7 @@ export const sendDockerCleanupNotifications = async (
const notificationList = await db.query.notifications.findMany({
where: and(
eq(notifications.dockerCleanup, true),
- eq(notifications.adminId, adminId),
+ eq(notifications.organizationId, organizationId),
),
with: {
email: true,
diff --git a/packages/server/src/utils/notifications/server-threshold.ts b/packages/server/src/utils/notifications/server-threshold.ts
new file mode 100644
index 000000000..2e63ba25a
--- /dev/null
+++ b/packages/server/src/utils/notifications/server-threshold.ts
@@ -0,0 +1,155 @@
+import { and, eq } from "drizzle-orm";
+import { db } from "../../db";
+import { notifications } from "../../db/schema";
+import {
+ sendDiscordNotification,
+ sendSlackNotification,
+ sendTelegramNotification,
+} from "./utils";
+
+interface ServerThresholdPayload {
+ Type: "CPU" | "Memory";
+ Value: number;
+ Threshold: number;
+ Message: string;
+ Timestamp: string;
+ Token: string;
+ ServerName: string;
+}
+
+export const sendServerThresholdNotifications = async (
+ organizationId: string,
+ payload: ServerThresholdPayload,
+) => {
+ const date = new Date(payload.Timestamp);
+ const unixDate = ~~(Number(date) / 1000);
+
+ const notificationList = await db.query.notifications.findMany({
+ where: and(
+ eq(notifications.serverThreshold, true),
+ eq(notifications.organizationId, organizationId),
+ ),
+ with: {
+ email: true,
+ discord: true,
+ telegram: true,
+ slack: true,
+ },
+ });
+
+ const typeEmoji = payload.Type === "CPU" ? "🔲" : "💾";
+ const typeColor = 0xff0000; // Rojo para indicar alerta
+
+ for (const notification of notificationList) {
+ const { discord, telegram, slack } = notification;
+
+ if (discord) {
+ const decorate = (decoration: string, text: string) =>
+ `${discord.decoration ? decoration : ""} ${text}`.trim();
+
+ await sendDiscordNotification(discord, {
+ title: decorate(">", `\`⚠️\` Server ${payload.Type} Alert`),
+ color: typeColor,
+ fields: [
+ {
+ name: decorate("`🏷️`", "Server Name"),
+ value: payload.ServerName,
+ inline: true,
+ },
+ {
+ name: decorate("`📅`", "Date"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate("`⌚`", "Time"),
+ value: ``,
+ inline: true,
+ },
+ {
+ name: decorate(typeEmoji, "Type"),
+ value: payload.Type,
+ inline: true,
+ },
+ {
+ name: decorate("📊", "Current Value"),
+ value: `${payload.Value.toFixed(2)}%`,
+ inline: true,
+ },
+ {
+ name: decorate("⚠️", "Threshold"),
+ value: `${payload.Threshold.toFixed(2)}%`,
+ inline: true,
+ },
+ {
+ name: decorate("`📜`", "Message"),
+ value: `\`\`\`${payload.Message}\`\`\``,
+ },
+ ],
+ timestamp: date.toISOString(),
+ footer: {
+ text: "Dokploy Server Monitoring Alert",
+ },
+ });
+ }
+
+ if (telegram) {
+ await sendTelegramNotification(
+ telegram,
+ `
+ ⚠️ Server ${payload.Type} Alert
+ Server Name: ${payload.ServerName}
+ Type: ${payload.Type}
+ Current Value: ${payload.Value.toFixed(2)}%
+ Threshold: ${payload.Threshold.toFixed(2)}%
+ Message: ${payload.Message}
+ Time: ${date.toLocaleString()}
+ `,
+ );
+ }
+
+ if (slack) {
+ const { channel } = slack;
+ await sendSlackNotification(slack, {
+ channel: channel,
+ attachments: [
+ {
+ color: "#FF0000",
+ pretext: `:warning: *Server ${payload.Type} Alert*`,
+ fields: [
+ {
+ title: "Server Name",
+ value: payload.ServerName,
+ short: true,
+ },
+ {
+ title: "Type",
+ value: payload.Type,
+ short: true,
+ },
+ {
+ title: "Current Value",
+ value: `${payload.Value.toFixed(2)}%`,
+ short: true,
+ },
+ {
+ title: "Threshold",
+ value: `${payload.Threshold.toFixed(2)}%`,
+ short: true,
+ },
+ {
+ title: "Message",
+ value: payload.Message,
+ },
+ {
+ title: "Time",
+ value: date.toLocaleString(),
+ short: true,
+ },
+ ],
+ },
+ ],
+ });
+ }
+ }
+};
diff --git a/packages/server/src/utils/notifications/utils.ts b/packages/server/src/utils/notifications/utils.ts
index 4f8bb1a5e..dd552cf3e 100644
--- a/packages/server/src/utils/notifications/utils.ts
+++ b/packages/server/src/utils/notifications/utils.ts
@@ -68,6 +68,7 @@ export const sendTelegramNotification = async (
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
chat_id: connection.chatId,
+ message_thread_id: connection.messageThreadId,
text: messageText,
parse_mode: "HTML",
disable_web_page_preview: true,
diff --git a/packages/server/src/utils/process/execAsync.ts b/packages/server/src/utils/process/execAsync.ts
index 19a16ac1e..aee1e821a 100644
--- a/packages/server/src/utils/process/execAsync.ts
+++ b/packages/server/src/utils/process/execAsync.ts
@@ -27,7 +27,7 @@ export const execAsyncRemote = async (
throw err;
}
stream
- .on("close", (code: number, signal: string) => {
+ .on("close", (code: number, _signal: string) => {
conn.end();
if (code === 0) {
resolve({ stdout, stderr });
diff --git a/packages/server/src/utils/providers/bitbucket.ts b/packages/server/src/utils/providers/bitbucket.ts
index 7059e65f7..dd98a93bd 100644
--- a/packages/server/src/utils/providers/bitbucket.ts
+++ b/packages/server/src/utils/providers/bitbucket.ts
@@ -176,7 +176,6 @@ export const getBitbucketCloneCommand = async (
bitbucketBranch,
bitbucketId,
serverId,
- bitbucket,
} = entity;
if (!serverId) {
diff --git a/packages/server/src/utils/providers/git.ts b/packages/server/src/utils/providers/git.ts
index 6377b557e..c26af3af2 100644
--- a/packages/server/src/utils/providers/git.ts
+++ b/packages/server/src/utils/providers/git.ts
@@ -69,6 +69,7 @@ export const cloneGitRepository = async (
});
}
+ const { port } = sanitizeRepoPathSSH(customGitUrl);
await spawnAsync(
"git",
[
@@ -91,7 +92,7 @@ export const cloneGitRepository = async (
env: {
...process.env,
...(customGitSSHKeyId && {
- GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath} -o UserKnownHostsFile=${knownHostsPath}`,
+ GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath}${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`,
}),
},
},
@@ -168,7 +169,8 @@ export const getCustomGitCloneCommand = async (
);
if (customGitSSHKeyId) {
const sshKey = await findSSHKeyById(customGitSSHKeyId);
- const gitSshCommand = `ssh -i /tmp/id_rsa -o UserKnownHostsFile=${knownHostsPath}`;
+ const { port } = sanitizeRepoPathSSH(customGitUrl);
+ const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`;
command.push(
`
echo "${sshKey.privateKey}" > /tmp/id_rsa
@@ -304,6 +306,7 @@ export const cloneGitRawRepository = async (entity: {
});
}
+ const { port } = sanitizeRepoPathSSH(customGitUrl);
await spawnAsync(
"git",
[
@@ -317,12 +320,12 @@ export const cloneGitRawRepository = async (entity: {
outputPath,
"--progress",
],
- (data) => {},
+ (_data) => {},
{
env: {
...process.env,
...(customGitSSHKeyId && {
- GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath} -o UserKnownHostsFile=${knownHostsPath}`,
+ GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath}${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`,
}),
},
},
@@ -381,7 +384,8 @@ export const cloneRawGitRepositoryRemote = async (compose: Compose) => {
command.push(`mkdir -p ${outputPath};`);
if (customGitSSHKeyId) {
const sshKey = await findSSHKeyById(customGitSSHKeyId);
- const gitSshCommand = `ssh -i /tmp/id_rsa -o UserKnownHostsFile=${knownHostsPath}`;
+ const { port } = sanitizeRepoPathSSH(customGitUrl);
+ const gitSshCommand = `ssh -i /tmp/id_rsa${port ? ` -p ${port}` : ""} -o UserKnownHostsFile=${knownHostsPath}`;
command.push(
`
echo "${sshKey.privateKey}" > /tmp/id_rsa
diff --git a/packages/server/src/utils/providers/gitlab.ts b/packages/server/src/utils/providers/gitlab.ts
index 096f9e284..c380a9203 100644
--- a/packages/server/src/utils/providers/gitlab.ts
+++ b/packages/server/src/utils/providers/gitlab.ts
@@ -162,8 +162,6 @@ export const getGitlabCloneCommand = async (
) => {
const {
appName,
- gitlabRepository,
- gitlabOwner,
gitlabPathNamespace,
gitlabBranch,
gitlabId,
@@ -268,7 +266,7 @@ export const getGitlabRepositories = async (gitlabId?: string) => {
if (groupName) {
return full_path.toLowerCase().includes(groupName) && kind === "group";
}
- return kind === "user";
+ return kind === "member";
});
const mappedRepositories = filteredRepos.map((repo: any) => {
return {
@@ -328,14 +326,7 @@ export const getGitlabBranches = async (input: {
};
export const cloneRawGitlabRepository = async (entity: Compose) => {
- const {
- appName,
- gitlabRepository,
- gitlabOwner,
- gitlabBranch,
- gitlabId,
- gitlabPathNamespace,
- } = entity;
+ const { appName, gitlabBranch, gitlabId, gitlabPathNamespace } = entity;
if (!gitlabId) {
throw new TRPCError({
@@ -442,7 +433,7 @@ export const testGitlabConnection = async (
if (groupName) {
return full_path.toLowerCase().includes(groupName) && kind === "group";
}
- return kind === "user";
+ return kind === "member";
});
return filteredRepos.length;
diff --git a/packages/server/src/utils/traefik/application.ts b/packages/server/src/utils/traefik/application.ts
index 4434d8585..61150abf6 100644
--- a/packages/server/src/utils/traefik/application.ts
+++ b/packages/server/src/utils/traefik/application.ts
@@ -67,7 +67,7 @@ export const removeTraefikConfig = async (
if (fs.existsSync(configPath)) {
await fs.promises.unlink(configPath);
}
- } catch (error) {}
+ } catch (_error) {}
};
export const removeTraefikConfigRemote = async (
@@ -78,7 +78,7 @@ export const removeTraefikConfigRemote = async (
const { DYNAMIC_TRAEFIK_PATH } = paths(true);
const configPath = path.join(DYNAMIC_TRAEFIK_PATH, `${appName}.yml`);
await execAsyncRemote(serverId, `rm ${configPath}`);
- } catch (error) {}
+ } catch (_error) {}
};
export const loadOrCreateConfig = (appName: string): FileConfig => {
@@ -110,7 +110,7 @@ export const loadOrCreateConfigRemote = async (
http: { routers: {}, services: {} },
};
return parsedConfig;
- } catch (err) {
+ } catch (_err) {
return fileConfig;
}
};
@@ -132,7 +132,7 @@ export const readRemoteConfig = async (serverId: string, appName: string) => {
const { stdout } = await execAsyncRemote(serverId, `cat ${configPath}`);
if (!stdout) return null;
return stdout;
- } catch (err) {
+ } catch (_err) {
return null;
}
};
diff --git a/packages/server/src/utils/traefik/domain.ts b/packages/server/src/utils/traefik/domain.ts
index a6c878e72..1ae3c05a1 100644
--- a/packages/server/src/utils/traefik/domain.ts
+++ b/packages/server/src/utils/traefik/domain.ts
@@ -122,13 +122,25 @@ export const createRouterConfig = async (
if ((entryPoint === "websecure" && https) || !https) {
// redirects
for (const redirect of redirects) {
- const middlewareName = `redirect-${appName}-${redirect.uniqueConfigKey}`;
+ let middlewareName = `redirect-${appName}-${redirect.uniqueConfigKey}`;
+ if (domain.domainType === "preview") {
+ middlewareName = `redirect-${appName.replace(
+ /^preview-(.+)-[^-]+$/,
+ "$1",
+ )}-${redirect.uniqueConfigKey}`;
+ }
routerConfig.middlewares?.push(middlewareName);
}
// security
if (security.length > 0) {
- const middlewareName = `auth-${appName}`;
+ let middlewareName = `auth-${appName}`;
+ if (domain.domainType === "preview") {
+ middlewareName = `auth-${appName.replace(
+ /^preview-(.+)-[^-]+$/,
+ "$1",
+ )}`;
+ }
routerConfig.middlewares?.push(middlewareName);
}
}
diff --git a/packages/server/src/utils/traefik/middleware.ts b/packages/server/src/utils/traefik/middleware.ts
index 60345f66c..934d637e1 100644
--- a/packages/server/src/utils/traefik/middleware.ts
+++ b/packages/server/src/utils/traefik/middleware.ts
@@ -95,7 +95,7 @@ export const loadRemoteMiddlewares = async (serverId: string) => {
}
const config = load(stdout) as FileConfig;
return config;
- } catch (error) {
+ } catch (_) {
throw new Error(`File not found: ${configPath}`);
}
};
diff --git a/packages/server/src/utils/traefik/web-server.ts b/packages/server/src/utils/traefik/web-server.ts
index 0aa4d35d5..78046c673 100644
--- a/packages/server/src/utils/traefik/web-server.ts
+++ b/packages/server/src/utils/traefik/web-server.ts
@@ -1,14 +1,14 @@
import { existsSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { paths } from "@dokploy/server/constants";
-import type { Admin } from "@dokploy/server/services/admin";
+import type { User } from "@dokploy/server/services/user";
import { dump, load } from "js-yaml";
import { loadOrCreateConfig, writeTraefikConfig } from "./application";
import type { FileConfig } from "./file-types";
import type { MainTraefikConfig } from "./types";
export const updateServerTraefik = (
- admin: Admin | null,
+ user: User | null,
newHost: string | null,
) => {
const appName = "dokploy";
@@ -22,7 +22,7 @@ export const updateServerTraefik = (
if (currentRouterConfig && newHost) {
currentRouterConfig.rule = `Host(\`${newHost}\`)`;
- if (admin?.certificateType === "letsencrypt") {
+ if (user?.certificateType === "letsencrypt") {
config.http.routers[`${appName}-router-app-secure`] = {
...currentRouterConfig,
entryPoints: ["websecure"],
diff --git a/packages/server/src/verification/send-verification-email.tsx b/packages/server/src/verification/send-verification-email.tsx
new file mode 100644
index 000000000..c673c0f77
--- /dev/null
+++ b/packages/server/src/verification/send-verification-email.tsx
@@ -0,0 +1,51 @@
+import {
+ sendDiscordNotification,
+ sendEmailNotification,
+} from "../utils/notifications/utils";
+export const sendEmail = async ({
+ email,
+ subject,
+ text,
+}: {
+ email: string;
+ subject: string;
+ text: string;
+}) => {
+ await sendEmailNotification(
+ {
+ fromAddress: process.env.SMTP_FROM_ADDRESS || "",
+ toAddresses: [email],
+ smtpServer: process.env.SMTP_SERVER || "",
+ smtpPort: Number(process.env.SMTP_PORT),
+ username: process.env.SMTP_USERNAME || "",
+ password: process.env.SMTP_PASSWORD || "",
+ },
+ subject,
+ text,
+ );
+
+ return true;
+};
+
+export const sendDiscordNotificationWelcome = async (email: string) => {
+ await sendDiscordNotification(
+ {
+ webhookUrl: process.env.DISCORD_WEBHOOK_URL || "",
+ },
+ {
+ title: "New User Registered",
+ color: 0x00ff00,
+ fields: [
+ {
+ name: "Email",
+ value: email,
+ inline: true,
+ },
+ ],
+ timestamp: new Date(),
+ footer: {
+ text: "Dokploy User Registration Notification",
+ },
+ },
+ );
+};
diff --git a/packages/server/tsconfig.server.json b/packages/server/tsconfig.server.json
index 7f349eb82..33777c025 100644
--- a/packages/server/tsconfig.server.json
+++ b/packages/server/tsconfig.server.json
@@ -1,6 +1,7 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
+ "disableSizeLimit": true,
"module": "ESNext",
"outDir": "dist/",
"target": "ESNext",
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 7542278f0..703101ec5 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -17,7 +17,7 @@ importers:
version: 1.9.4
'@commitlint/cli':
specifier: ^19.3.0
- version: 19.3.0(@types/node@18.19.42)(typescript@5.5.3)
+ version: 19.3.0(@types/node@18.19.42)(typescript@5.7.2)
'@commitlint/config-conventional':
specifier: ^19.2.2
version: 19.2.2
@@ -127,6 +127,12 @@ importers:
'@hookform/resolvers':
specifier: ^3.9.0
version: 3.9.0(react-hook-form@7.52.1(react@18.2.0))
+ '@lucia-auth/adapter-drizzle':
+ specifier: 1.0.7
+ version: 1.0.7(lucia@3.2.0)
+ '@octokit/auth-app':
+ specifier: ^6.0.4
+ version: 6.1.1
'@octokit/webhooks':
specifier: ^13.2.7
version: 13.3.0
@@ -187,6 +193,9 @@ importers:
'@radix-ui/react-tooltip':
specifier: ^1.0.7
version: 1.1.2(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ '@react-email/components':
+ specifier: ^0.0.21
+ version: 0.0.21(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
'@stepperize/react':
specifier: 4.0.1
version: 4.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -220,6 +229,9 @@ importers:
'@xterm/addon-attach':
specifier: 0.10.0
version: 0.10.0(@xterm/xterm@5.5.0)
+ '@xterm/addon-clipboard':
+ specifier: 0.1.0
+ version: 0.1.0(@xterm/xterm@5.5.0)
'@xterm/xterm':
specifier: ^5.4.0
version: 5.5.0
@@ -231,7 +243,16 @@ importers:
version: 4.0.31(react@18.2.0)(zod@3.23.8)
bcrypt:
specifier: 5.1.1
- version: 5.1.1
+ version: 5.1.1(encoding@0.1.13)
+ better-auth:
+ specifier: 1.2.0
+ version: 1.2.0(typescript@5.5.3)
+ bl:
+ specifier: 6.0.11
+ version: 6.0.11
+ boxen:
+ specifier: ^7.1.1
+ version: 7.1.1
bullmq:
specifier: 5.4.2
version: 5.4.2
@@ -253,18 +274,24 @@ importers:
date-fns:
specifier: 3.6.0
version: 3.6.0
+ dockerode:
+ specifier: 4.0.2
+ version: 4.0.2
dotenv:
specifier: 16.4.5
version: 16.4.5
drizzle-orm:
- specifier: ^0.30.8
- version: 0.30.10(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)
+ specifier: ^0.39.1
+ version: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
drizzle-zod:
specifier: 0.5.1
- version: 0.5.1(drizzle-orm@0.30.10(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0))(zod@3.23.8)
+ version: 0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
fancy-ansi:
specifier: ^0.1.3
version: 0.1.3
+ hi-base32:
+ specifier: ^0.5.1
+ version: 0.5.1
i18next:
specifier: ^23.16.4
version: 23.16.5
@@ -297,28 +324,43 @@ importers:
version: 15.3.1(i18next@23.16.5)(next@15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-i18next@15.1.1(i18next@23.16.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react@18.2.0)
next-themes:
specifier: ^0.2.1
- version: 0.2.1(next@15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ version: 0.2.1(next@15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0))(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ node-os-utils:
+ specifier: 1.3.7
+ version: 1.3.7
node-pty:
specifier: 1.0.0
version: 1.0.0
node-schedule:
specifier: 2.1.1
version: 2.1.1
+ nodemailer:
+ specifier: 6.9.14
+ version: 6.9.14
octokit:
specifier: 3.1.2
version: 3.1.2
+ otpauth:
+ specifier: ^9.2.3
+ version: 9.3.4
postgres:
specifier: 3.4.4
version: 3.4.4
public-ip:
specifier: 6.0.2
version: 6.0.2
+ qrcode:
+ specifier: ^1.5.3
+ version: 1.5.4
react:
specifier: 18.2.0
version: 18.2.0
react-confetti-explosion:
specifier: 2.1.2
version: 2.1.2(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ react-day-picker:
+ specifier: 8.10.1
+ version: 8.10.1(date-fns@3.6.0)(react@18.2.0)
react-dom:
specifier: 18.2.0
version: 18.2.0(react@18.2.0)
@@ -334,6 +376,9 @@ importers:
recharts:
specifier: ^2.12.7
version: 2.12.7(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
+ rotating-file-stream:
+ specifier: 3.2.3
+ version: 3.2.3
slugify:
specifier: ^1.6.6
version: 1.6.6
@@ -357,7 +402,7 @@ importers:
version: 2.4.0
tailwindcss-animate:
specifier: ^1.0.7
- version: 1.0.7(tailwindcss@3.4.7)
+ version: 1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@18.19.42)(typescript@5.5.3)))
undici:
specifier: ^6.19.2
version: 6.19.4
@@ -398,9 +443,18 @@ importers:
'@types/node':
specifier: ^18.17.0
version: 18.19.42
+ '@types/node-os-utils':
+ specifier: 1.3.4
+ version: 1.3.4
'@types/node-schedule':
specifier: 2.1.6
version: 2.1.6
+ '@types/nodemailer':
+ specifier: ^6.4.15
+ version: 6.4.16
+ '@types/qrcode':
+ specifier: ^1.5.5
+ version: 1.5.5
'@types/react':
specifier: 18.3.5
version: 18.3.5
@@ -420,8 +474,8 @@ importers:
specifier: 10.4.12
version: 10.4.12(postcss@8.4.40)
drizzle-kit:
- specifier: ^0.21.1
- version: 0.21.4
+ specifier: ^0.30.4
+ version: 0.30.4
esbuild:
specifier: 0.20.2
version: 0.20.2
@@ -433,7 +487,7 @@ importers:
version: 4.11.0
tailwindcss:
specifier: ^3.4.1
- version: 3.4.7
+ version: 3.4.7(ts-node@10.9.2(@types/node@18.19.42)(typescript@5.5.3))
tsx:
specifier: ^4.7.0
version: 4.16.2
@@ -465,8 +519,8 @@ importers:
specifier: ^16.3.1
version: 16.4.5
drizzle-orm:
- specifier: ^0.30.8
- version: 0.30.10(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)
+ specifier: ^0.39.1
+ version: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
hono:
specifier: ^4.5.8
version: 4.5.8
@@ -507,27 +561,9 @@ importers:
packages/server:
dependencies:
- '@ai-sdk/anthropic':
- specifier: ^1.0.6
- version: 1.0.8(zod@3.23.8)
- '@ai-sdk/azure':
- specifier: ^1.0.15
- version: 1.0.18(zod@3.23.8)
- '@ai-sdk/cohere':
- specifier: ^1.0.6
- version: 1.0.8(zod@3.23.8)
- '@ai-sdk/deepinfra':
- specifier: ^0.0.4
- version: 0.0.4(zod@3.23.8)
- '@ai-sdk/mistral':
- specifier: ^1.0.6
- version: 1.0.8(zod@3.23.8)
- '@ai-sdk/openai':
- specifier: ^1.0.12
- version: 1.0.16(zod@3.23.8)
- '@ai-sdk/openai-compatible':
- specifier: ^0.0.13
- version: 0.0.13(zod@3.23.8)
+ '@better-auth/utils':
+ specifier: 0.2.3
+ version: 0.2.3
'@faker-js/faker':
specifier: ^8.4.1
version: 8.4.1
@@ -537,6 +573,12 @@ importers:
'@octokit/auth-app':
specifier: ^6.0.4
version: 6.1.1
+ '@oslojs/crypto':
+ specifier: 1.0.1
+ version: 1.0.1
+ '@oslojs/encoding':
+ specifier: 1.1.0
+ version: 1.1.0
'@react-email/components':
specifier: ^0.0.21
version: 0.0.21(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
@@ -551,7 +593,10 @@ importers:
version: 4.0.31(react@18.2.0)(zod@3.23.8)
bcrypt:
specifier: 5.1.1
- version: 5.1.1
+ version: 5.1.1(encoding@0.1.13)
+ better-auth:
+ specifier: 1.2.0
+ version: 1.2.0(typescript@5.5.3)
bl:
specifier: 6.0.11
version: 6.0.11
@@ -567,12 +612,15 @@ importers:
dotenv:
specifier: 16.4.5
version: 16.4.5
+ drizzle-dbml-generator:
+ specifier: 0.10.0
+ version: 0.10.0(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))
drizzle-orm:
- specifier: ^0.30.8
- version: 0.30.10(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)
+ specifier: ^0.39.1
+ version: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
drizzle-zod:
specifier: 0.5.1
- version: 0.5.1(drizzle-orm@0.30.10(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0))(zod@3.23.8)
+ version: 0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8)
hi-base32:
specifier: ^0.5.1
version: 0.5.1
@@ -683,8 +731,8 @@ importers:
specifier: 8.5.10
version: 8.5.10
drizzle-kit:
- specifier: ^0.21.1
- version: 0.21.4
+ specifier: ^0.30.4
+ version: 0.30.4
esbuild:
specifier: 0.20.2
version: 0.20.2
@@ -696,7 +744,7 @@ importers:
version: 8.4.40
tailwindcss:
specifier: ^3.4.1
- version: 3.4.7
+ version: 3.4.7(ts-node@10.9.2(@types/node@18.19.42)(typescript@5.5.3))
tsc-alias:
specifier: 1.8.10
version: 1.8.10
@@ -825,6 +873,12 @@ packages:
'@balena/dockerignore@1.0.2':
resolution: {integrity: sha512-wMue2Sy4GAVTk6Ic4tJVcnfdau+gx2EnG7S+uAEe+TWJFqE4YoWN4/H8MSLj4eYJKxGg26lZwboEniNiNwZQ6Q==}
+ '@better-auth/utils@0.2.3':
+ resolution: {integrity: sha512-Ap1GaSmo6JYhJhxJOpUB0HobkKPTNzfta+bLV89HfpyCAHN7p8ntCrmNFHNAVD0F6v0mywFVEUg1FUhNCc81Rw==}
+
+ '@better-fetch/fetch@1.1.15':
+ resolution: {integrity: sha512-0Bl8YYj1f8qCTNHeSn5+1DWv2hy7rLBrQ8rS8Y9XYloiwZEfc3k4yspIG0llRxafxqhGCwlGRg+F8q1HZRCMXA==}
+
'@biomejs/biome@1.9.4':
resolution: {integrity: sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==}
engines: {node: '>=14.21.3'}
@@ -988,12 +1042,19 @@ packages:
resolution: {integrity: sha512-tpyc+7i6bPG9mvaBbtKUeghfyZSDgWquIDfMgqYtTbmZ9Y9VzEm2je9EYcQ0aoz5o7NvGS+rcDec93yO08MHYA==}
engines: {node: '>=v18'}
+ '@cspotcode/source-map-support@0.8.1':
+ resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
+ engines: {node: '>=12'}
+
'@dokploy/trpc-openapi@0.0.4':
resolution: {integrity: sha512-a7VKunKu9arq57bP9MPH7ikJuKfT5SILnNy70vMqf1stm5IrqMG3Y7CIFprFe0DZiw3bwjue0KpETIATBftN6w==}
peerDependencies:
'@trpc/server': ^10.0.0
zod: ^3.14.4
+ '@drizzle-team/brocli@0.10.2':
+ resolution: {integrity: sha512-z33Il7l5dKjUgGULTqBsQBQwckHh5AbIuxhdsIxDDiZAzBOrZO6q9ogcWC65kU382AfynTfgNumVcNIjuIua6w==}
+
'@emnapi/core@0.45.0':
resolution: {integrity: sha512-DPWjcUDQkCeEM4VnljEOEcXdAD7pp8zSZsgOujk/LGIwCXWbXJngin+MO4zbH429lzeC3WbYLGjE2MaUOwzpyw==}
@@ -1582,9 +1643,15 @@ packages:
'@floating-ui/utils@0.2.5':
resolution: {integrity: sha512-sTcG+QZ6fdEUObICavU+aB3Mp8HY4n14wYHdxK4fXjPmv3PXZZeY5RaguJmGyeH/CJQhX3fqKUtS4qc1LoHwhQ==}
+ '@gar/promisify@1.1.3':
+ resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==}
+
'@hapi/bourne@3.0.0':
resolution: {integrity: sha512-Waj1cwPXJDucOib4a3bAISsKJVb15MKi9IvmTI/7ssVEm6sywXGjVJDhl6/umt1pK1ZS7PacXU3A1PmFKHEZ2w==}
+ '@hexagon/base64@1.1.28':
+ resolution: {integrity: sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==}
+
'@hono/node-server@1.12.1':
resolution: {integrity: sha512-C9l+08O8xtXB7Ppmy8DjBFH1hYji7JKzsU32Yt1poIIbdPp6S7aOI8IldDHD9YFJ55lv2c21ovNrmxatlHfhAg==}
engines: {node: '>=18.14.1'}
@@ -1737,6 +1804,9 @@ packages:
'@jridgewell/trace-mapping@0.3.25':
resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==}
+ '@jridgewell/trace-mapping@0.3.9':
+ resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
+
'@jsonjoy.com/base64@1.1.2':
resolution: {integrity: sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA==}
engines: {node: '>=10.0'}
@@ -1761,6 +1831,9 @@ packages:
'@leichtgewicht/ip-codec@2.0.5':
resolution: {integrity: sha512-Vo+PSpZG2/fmgmiNzYK9qWRh8h/CHrwD0mo1h1DzL4yzHNSfWYujGTYsWGreD000gcgmZ7K4Ys6Tx9TxtsKdDw==}
+ '@levischuck/tiny-cbor@0.2.11':
+ resolution: {integrity: sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==}
+
'@lezer/common@1.2.1':
resolution: {integrity: sha512-yemX0ZD2xS/73llMZIK6KplkjIjf2EvAHcinDi/TfJ9hS25G0388+ClHt6/3but0oOxinTcQHJLDXh6w1crzFQ==}
@@ -1871,10 +1944,17 @@ packages:
cpu: [x64]
os: [win32]
+ '@noble/ciphers@0.6.0':
+ resolution: {integrity: sha512-mIbq/R9QXk5/cTfESb1OKtyFnk7oc1Om/8onA1158K9/OZUQFDEVy55jVTato+xmp3XX6F6Qh0zz0Nc1AxAlRQ==}
+
'@noble/hashes@1.5.0':
resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==}
engines: {node: ^14.21.3 || >=16}
+ '@noble/hashes@1.7.1':
+ resolution: {integrity: sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==}
+ engines: {node: ^14.21.3 || >=16}
+
'@node-rs/argon2-android-arm-eabi@1.7.0':
resolution: {integrity: sha512-udDqkr5P9E+wYX1SZwAVPdyfYvaF4ry9Tm+R9LkfSHbzWH0uhU6zjIwNRp7m+n4gx691rk+lqqDAIP8RLKwbhg==}
engines: {node: '>= 10'}
@@ -2061,6 +2141,14 @@ packages:
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
engines: {node: '>= 8'}
+ '@npmcli/fs@1.1.1':
+ resolution: {integrity: sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==}
+
+ '@npmcli/move-file@1.1.2':
+ resolution: {integrity: sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==}
+ engines: {node: '>=10'}
+ deprecated: This functionality has been moved to @npmcli/fs
+
'@octokit/app@14.1.0':
resolution: {integrity: sha512-g3uEsGOQCBl1+W1rgfwoRFUIR6PtvB2T1E4RpygeUU5LrLvlOqcxrt5lfykIeRpUPpupreGJUYl70fqMDXdTpw==}
engines: {node: '>= 18'}
@@ -2192,9 +2280,32 @@ packages:
'@one-ini/wasm@0.1.1':
resolution: {integrity: sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==}
- '@opentelemetry/api@1.9.0':
- resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==}
- engines: {node: '>=8.0.0'}
+ '@oslojs/asn1@1.0.0':
+ resolution: {integrity: sha512-zw/wn0sj0j0QKbIXfIlnEcTviaCzYOY3V5rAyjR6YtOByFtJiT574+8p9Wlach0lZH9fddD4yb9laEAIl4vXQA==}
+
+ '@oslojs/binary@1.0.0':
+ resolution: {integrity: sha512-9RCU6OwXU6p67H4NODbuxv2S3eenuQ4/WFLrsq+K/k682xrznH5EVWA7N4VFk9VYVcbFtKqur5YQQZc0ySGhsQ==}
+
+ '@oslojs/crypto@1.0.1':
+ resolution: {integrity: sha512-7n08G8nWjAr/Yu3vu9zzrd0L9XnrJfpMioQcvCMxBIiF5orECHe5/3J0jmXRVvgfqMm/+4oxlQ+Sq39COYLcNQ==}
+
+ '@oslojs/encoding@1.1.0':
+ resolution: {integrity: sha512-70wQhgYmndg4GCPxPPxPGevRKqTIJ2Nh4OkiMWmDAVYsTQ+Ta7Sq+rPevXyXGdzr30/qZBnyOalCszoMxlyldQ==}
+
+ '@peculiar/asn1-android@2.3.15':
+ resolution: {integrity: sha512-8U2TIj59cRlSXTX2d0mzUKP7whfWGFMzTeC3qPgAbccXFrPNZLaDhpNEdG5U2QZ/tBv/IHlCJ8s+KYXpJeop6w==}
+
+ '@peculiar/asn1-ecc@2.3.15':
+ resolution: {integrity: sha512-/HtR91dvgog7z/WhCVdxZJ/jitJuIu8iTqiyWVgRE9Ac5imt2sT/E4obqIVGKQw7PIy+X6i8lVBoT6wC73XUgA==}
+
+ '@peculiar/asn1-rsa@2.3.15':
+ resolution: {integrity: sha512-p6hsanvPhexRtYSOHihLvUUgrJ8y0FtOM97N5UEpC+VifFYyZa0iZ5cXjTkZoDwxJ/TTJ1IJo3HVTB2JJTpXvg==}
+
+ '@peculiar/asn1-schema@2.3.15':
+ resolution: {integrity: sha512-QPeD8UA8axQREpgR5UTAfu2mqQmm97oUqahDtNdBcfj3qAnoXzFdQW+aNf/tD2WVXF8Fhmftxoj0eMIT++gX2w==}
+
+ '@peculiar/asn1-x509@2.3.15':
+ resolution: {integrity: sha512-0dK5xqTqSLaxv1FHXIcd4Q/BZNuopg+u1l23hT9rOmQ1g4dNtw0g/RnEi+TboB0gOwGtrWn269v27cMgchFIIg==}
'@pkgjs/parseargs@0.11.0':
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
@@ -3207,6 +3318,13 @@ packages:
'@selderee/plugin-htmlparser2@0.11.0':
resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
+ '@simplewebauthn/browser@13.1.0':
+ resolution: {integrity: sha512-WuHZ/PYvyPJ9nxSzgHtOEjogBhwJfC8xzYkPC+rR/+8chl/ft4ngjiK8kSU5HtRJfczupyOh33b25TjYbvwAcg==}
+
+ '@simplewebauthn/server@13.1.1':
+ resolution: {integrity: sha512-1hsLpRHfSuMB9ee2aAdh0Htza/X3f4djhYISrggqGe3xopNjOcePiSDkDDoPzDYaaMCrbqGP1H2TYU7bgL9PmA==}
+ engines: {node: '>=20.0.0'}
+
'@sinclair/typebox@0.27.8':
resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==}
@@ -3353,6 +3471,10 @@ packages:
resolution: {integrity: sha512-IqREj9ADoml9zCAouIG/5kCGoyIxPFdqdyoxis9FisXFi5vT+iYfEfLosq4xkU/iDbMcEuAj+X8dWRLvKYDNoQ==}
engines: {node: '>=12'}
+ '@tootallnate/once@1.1.2':
+ resolution: {integrity: sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==}
+ engines: {node: '>= 6'}
+
'@trpc/client@10.45.2':
resolution: {integrity: sha512-ykALM5kYWTLn1zYuUOZ2cPWlVfrXhc18HzBDyRhoPYN0jey4iQHEFSEowfnhg1RvYnrAVjNBgHNeSAXjrDbGwg==}
peerDependencies:
@@ -3381,6 +3503,18 @@ packages:
'@trpc/server@10.45.2':
resolution: {integrity: sha512-wOrSThNNE4HUnuhJG6PfDRp4L2009KDVxsd+2VYH8ro6o/7/jwYZ8Uu5j+VaW+mOmc8EHerHzGcdbGNQSAUPgg==}
+ '@tsconfig/node10@1.0.11':
+ resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==}
+
+ '@tsconfig/node12@1.0.11':
+ resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
+
+ '@tsconfig/node14@1.0.3':
+ resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
+
+ '@tsconfig/node16@1.0.4':
+ resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
+
'@tybys/wasm-util@0.8.3':
resolution: {integrity: sha512-Z96T/L6dUFFxgFJ+pQtkPpne9q7i6kIPYCFnQBHSgSPV9idTsKfIhCss0h5iM9irweZCatkrdeP8yi5uM1eX6Q==}
@@ -3664,6 +3798,11 @@ packages:
peerDependencies:
'@xterm/xterm': ^5.0.0
+ '@xterm/addon-clipboard@0.1.0':
+ resolution: {integrity: sha512-zdoM7p53T5sv/HbRTyp4hY0kKmEQ3MZvAvEtiXqNIHc/JdpqwByCtsTaQF5DX2n4hYdXRPO4P/eOS0QEhX1nPw==}
+ peerDependencies:
+ '@xterm/xterm': ^5.4.0
+
'@xterm/xterm@5.5.0':
resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
@@ -3714,6 +3853,10 @@ packages:
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
engines: {node: '>= 6.0.0'}
+ agentkeepalive@4.6.0:
+ resolution: {integrity: sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==}
+ engines: {node: '>= 8.0.0'}
+
aggregate-error@3.1.0:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
@@ -3807,6 +3950,14 @@ packages:
engines: {node: '>=10'}
deprecated: This package is no longer supported.
+ are-we-there-yet@3.0.1:
+ resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==}
+ engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+ deprecated: This package is no longer supported.
+
+ arg@4.1.3:
+ resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
+
arg@5.0.2:
resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==}
@@ -3830,6 +3981,10 @@ packages:
asn1@0.2.6:
resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==}
+ asn1js@3.0.5:
+ resolution: {integrity: sha512-FVnvrKJwpt9LP2lAMl8qZswRNm3T4q9CON+bxldk2iwk3FFpuwhx2FfinyitizWHsVYyaY+y5JzDR0rCMV5yTQ==}
+ engines: {node: '>=12.0.0'}
+
assertion-error@1.1.0:
resolution: {integrity: sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==}
@@ -3875,10 +4030,19 @@ packages:
before-after-hook@2.2.3:
resolution: {integrity: sha512-NzUnlZexiaH/46WDhANlyR2bXRopNg4F/zuSA3OpZnllCUgRaOF2znDioDWrmbNVsuZk6l9pMquQB38cfBZwkQ==}
+ better-auth@1.2.0:
+ resolution: {integrity: sha512-eIRGOXfix25bh4fgs8jslZAZssufpIkxfEeEokQu5G4wICoDee1wPctkFb8v80PvhtI4dPm28SuAoZaAdRc6Wg==}
+
+ better-call@1.0.3:
+ resolution: {integrity: sha512-DUKImKoDIy5UtCvQbHTg0wuBRse6gu1Yvznn7+1B3I5TeY8sclRPFce0HI+4WF2bcb+9PqmkET8nXZubrHQh9A==}
+
binary-extensions@2.3.0:
resolution: {integrity: sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==}
engines: {node: '>=8'}
+ bindings@1.5.0:
+ resolution: {integrity: sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==}
+
bl@4.1.0:
resolution: {integrity: sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==}
@@ -3941,6 +4105,10 @@ packages:
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
engines: {node: '>=8'}
+ cacache@15.3.0:
+ resolution: {integrity: sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==}
+ engines: {node: '>= 10'}
+
cacheable-lookup@7.0.0:
resolution: {integrity: sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==}
engines: {node: '>=14.16'}
@@ -4044,10 +4212,6 @@ packages:
resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==}
engines: {node: '>=10'}
- cli-color@2.0.4:
- resolution: {integrity: sha512-zlnpg0jNcibNrO7GG9IeHH7maWFeCz+Ja1wx/7tZNU5ASSSSZ+/qZciM0/LHCYxSdqv5h2sdbQ/PXYdOuetXvA==}
- engines: {node: '>=0.10'}
-
cli-cursor@5.0.0:
resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
engines: {node: '>=18'}
@@ -4230,6 +4394,9 @@ packages:
resolution: {integrity: sha512-9IkYqtX3YHPCzoVg1Py+o9057a3i0fp7S530UWokCSaFVTc7CwXPRiOjRjBQQ18ZCNafx78YfnG+HALxtVmOGA==}
engines: {node: '>=10.0.0'}
+ create-require@1.1.1:
+ resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
+
crelt@1.0.6:
resolution: {integrity: sha512-VQ2MBenTq1fWZUH9DJNGti7kKv6EeAuYr3cLwxUWhIu1baTaXh4Ib5W2CqHVqib4/MqbYGJqiL3Zb8GJZr3l4g==}
@@ -4310,10 +4477,6 @@ packages:
resolution: {integrity: sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==}
engines: {node: '>=12'}
- d@1.0.2:
- resolution: {integrity: sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==}
- engines: {node: '>=0.12'}
-
dargs@8.1.0:
resolution: {integrity: sha512-wAV9QHOsNbwnWdNW2FYvE1P56wtgSbM+3SZcdGiWQILwVjACCXDCI3Ai8QlCjMDB8YK5zySiXZYBiwGmNY3lnw==}
engines: {node: '>=12'}
@@ -4428,8 +4591,9 @@ packages:
resolution: {integrity: sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
- difflib@0.2.4:
- resolution: {integrity: sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==}
+ diff@4.0.2:
+ resolution: {integrity: sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==}
+ engines: {node: '>=0.3.1'}
dijkstrajs@1.0.3:
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
@@ -4488,25 +4652,29 @@ packages:
resolution: {integrity: sha512-pYxfDYpued//QpnLIm4Avk7rsNtAtQkUES2cwAYSvD/wd2pKD71gN2Ebj3e7klzXwjocvE8c5vx/1fxwpqmSxA==}
engines: {node: '>=4'}
- dreamopt@0.8.0:
- resolution: {integrity: sha512-vyJTp8+mC+G+5dfgsY+r3ckxlz+QMX40VjPQsZc5gxVAxLmi64TBoVkP54A/pRAXMXsbu2GMMBrZPxNv23waMg==}
- engines: {node: '>=0.4.0'}
+ drizzle-dbml-generator@0.10.0:
+ resolution: {integrity: sha512-cMZq9E3U3RlmE0uBeXyc6oWJ0royOkC6HiTlc9LDeMe+W87poZTzKoNYUyAxZrs4Q1RQtob+cGKiefV4ZoI8HA==}
+ peerDependencies:
+ drizzle-orm: '>=0.36.0'
- drizzle-kit@0.21.4:
- resolution: {integrity: sha512-Nxcc1ONJLRgbhmR+azxjNF9Ly9privNLEIgW53c92whb4xp8jZLH1kMCh/54ci1mTMuYxPdOukqLwJ8wRudNwA==}
+ drizzle-kit@0.30.4:
+ resolution: {integrity: sha512-B2oJN5UkvwwNHscPWXDG5KqAixu7AUzZ3qbe++KU9SsQ+cZWR4DXEPYcvWplyFAno0dhRJECNEhNxiDmFaPGyQ==}
hasBin: true
- drizzle-orm@0.30.10:
- resolution: {integrity: sha512-IRy/QmMWw9lAQHpwbUh1b8fcn27S/a9zMIzqea1WNOxK9/4EB8gIo+FZWLiPXzl2n9ixGSv8BhsLZiOppWEwBw==}
+ drizzle-orm@0.39.1:
+ resolution: {integrity: sha512-2bDHlzTY31IDmrYn8i+ZZrxK8IyBD4mPZ7JmZdVDQj2tpBZXs/gxB/1kK5pSvkjxPUMNOVsTnoGkSltgjuJwcA==}
peerDependencies:
'@aws-sdk/client-rds-data': '>=3'
- '@cloudflare/workers-types': '>=3'
- '@electric-sql/pglite': '>=0.1.1'
- '@libsql/client': '*'
- '@neondatabase/serverless': '>=0.1'
+ '@cloudflare/workers-types': '>=4'
+ '@electric-sql/pglite': '>=0.2.0'
+ '@libsql/client': '>=0.10.0'
+ '@libsql/client-wasm': '>=0.10.0'
+ '@neondatabase/serverless': '>=0.10.0'
'@op-engineering/op-sqlite': '>=2'
'@opentelemetry/api': ^1.4.1
'@planetscale/database': '>=1'
+ '@prisma/client': '*'
+ '@tidbcloud/serverless': '*'
'@types/better-sqlite3': '*'
'@types/pg': '*'
'@types/react': 18.3.5
@@ -4515,12 +4683,13 @@ packages:
'@xata.io/client': '*'
better-sqlite3: '>=7'
bun-types: '*'
- expo-sqlite: '>=13.2.0'
+ expo-sqlite: '>=14.0.0'
knex: '*'
kysely: '*'
mysql2: '>=2'
pg: '>=8'
postgres: '>=3'
+ prisma: '*'
react: '>=18'
sql.js: '>=1'
sqlite3: '>=5'
@@ -4533,6 +4702,8 @@ packages:
optional: true
'@libsql/client':
optional: true
+ '@libsql/client-wasm':
+ optional: true
'@neondatabase/serverless':
optional: true
'@op-engineering/op-sqlite':
@@ -4541,6 +4712,10 @@ packages:
optional: true
'@planetscale/database':
optional: true
+ '@prisma/client':
+ optional: true
+ '@tidbcloud/serverless':
+ optional: true
'@types/better-sqlite3':
optional: true
'@types/pg':
@@ -4569,6 +4744,8 @@ packages:
optional: true
postgres:
optional: true
+ prisma:
+ optional: true
react:
optional: true
sql.js:
@@ -4605,6 +4782,9 @@ packages:
emoji-regex@9.2.2:
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
+ encoding@0.1.13:
+ resolution: {integrity: sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==}
+
end-of-stream@1.4.4:
resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==}
@@ -4620,14 +4800,13 @@ packages:
resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==}
engines: {node: '>=6'}
- env-paths@3.0.0:
- resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
- engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
-
environment@1.1.0:
resolution: {integrity: sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==}
engines: {node: '>=18'}
+ err-code@2.0.3:
+ resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==}
+
error-ex@1.3.2:
resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==}
@@ -4642,20 +4821,6 @@ packages:
es-module-lexer@1.5.4:
resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==}
- es5-ext@0.10.64:
- resolution: {integrity: sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==}
- engines: {node: '>=0.10'}
-
- es6-iterator@2.0.3:
- resolution: {integrity: sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==}
-
- es6-symbol@3.1.4:
- resolution: {integrity: sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==}
- engines: {node: '>=0.12'}
-
- es6-weak-map@2.0.3:
- resolution: {integrity: sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==}
-
esbuild-plugin-alias@0.2.1:
resolution: {integrity: sha512-jyfL/pwPqaFXyKnj8lP8iLk6Z0m099uXR45aSN8Av1XD4vhvQutxxPzgA2bTcAwQpa1zCXDcWOlhFgyP3GKqhQ==}
@@ -4703,10 +4868,6 @@ packages:
resolution: {integrity: sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==}
engines: {node: '>=8.0.0'}
- esniff@2.0.1:
- resolution: {integrity: sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==}
- engines: {node: '>=0.10'}
-
esrecurse@4.3.0:
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
engines: {node: '>=4.0'}
@@ -4725,9 +4886,6 @@ packages:
estree-walker@3.0.3:
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
- event-emitter@0.3.5:
- resolution: {integrity: sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==}
-
event-target-shim@5.0.1:
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
engines: {node: '>=6'}
@@ -4754,12 +4912,6 @@ packages:
resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==}
engines: {node: '>=6'}
- ext@1.7.0:
- resolution: {integrity: sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==}
-
- extend@3.0.2:
- resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==}
-
fancy-ansi@0.1.3:
resolution: {integrity: sha512-tRQVTo5jjdSIiydqgzIIEZpKddzSsfGLsSVt6vWdjVm7fbvDTiQkyoPu6Z3dIPlAM4OZk0jP5jmTCX4G8WGgBw==}
@@ -4802,6 +4954,9 @@ packages:
fault@1.0.4:
resolution: {integrity: sha512-CJ0HCB5tL5fYTEA7ToAq5+kTwd++Borf1/bifxd9iT70QcXr4MRrO3Llf8Ifs70q+SJcGHFtnIE/Nw6giCtECA==}
+ file-uri-to-path@1.0.0:
+ resolution: {integrity: sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==}
+
fill-range@7.1.1:
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
engines: {node: '>=8'}
@@ -4872,6 +5027,11 @@ packages:
engines: {node: '>=10'}
deprecated: This package is no longer supported.
+ gauge@4.0.4:
+ resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==}
+ engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+ deprecated: This package is no longer supported.
+
generic-pool@3.9.0:
resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
engines: {node: '>= 4'}
@@ -4933,11 +5093,6 @@ packages:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
deprecated: Glob versions prior to v9 are no longer supported
- glob@8.1.0:
- resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==}
- engines: {node: '>=12'}
- deprecated: Glob versions prior to v9 are no longer supported
-
global-directory@4.0.1:
resolution: {integrity: sha512-wHTUcDUoZ1H5/0iVqEudYW4/kAlN5cZ3j/bXn0Dpbizl9iaUVeWSHqiOjsgk6OW2bkLclbBjzewBz6weQ1zA2Q==}
engines: {node: '>=18'}
@@ -4966,9 +5121,6 @@ packages:
h3@1.12.0:
resolution: {integrity: sha512-Zi/CcNeWBXDrFNlV0hUBJQR9F7a96RjMeAZweW/ZWkR9fuXrMcvKnSA63f/zZ9l0GgQOZDVHGvXivNN9PWOwhA==}
- hanji@0.0.5:
- resolution: {integrity: sha512-Abxw1Lq+TnYiL4BueXqMau222fPSPMFtya8HdpWsz/xVAhifXou71mPh/kY2+08RgFcVccjG3uZHs6K5HAe3zw==}
-
has-flag@3.0.0:
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
engines: {node: '>=4'}
@@ -5007,9 +5159,6 @@ packages:
hastscript@6.0.0:
resolution: {integrity: sha512-nDM6bvd7lIqDUiYEiu5Sl/+6ReP0BMk/2f4U/Rooccxkj0P5nm+acM5PrGJ/t5I8qPGiqZSE6hVAwZEdZIvP4w==}
- heap@0.2.7:
- resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==}
-
help-me@5.0.0:
resolution: {integrity: sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==}
@@ -5046,6 +5195,10 @@ packages:
resolution: {integrity: sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==}
engines: {node: '>= 0.8'}
+ http-proxy-agent@4.0.1:
+ resolution: {integrity: sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==}
+ engines: {node: '>= 6'}
+
http2-wrapper@2.2.1:
resolution: {integrity: sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==}
engines: {node: '>=10.19.0'}
@@ -5058,6 +5211,9 @@ packages:
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
engines: {node: '>=16.17.0'}
+ humanize-ms@1.2.1:
+ resolution: {integrity: sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==}
+
hyperdyperid@1.2.0:
resolution: {integrity: sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A==}
engines: {node: '>=10.18'}
@@ -5075,6 +5231,10 @@ packages:
resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==}
engines: {node: '>=0.10.0'}
+ iconv-lite@0.6.3:
+ resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
+ engines: {node: '>=0.10.0'}
+
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@@ -5093,6 +5253,10 @@ packages:
import-meta-resolve@4.1.0:
resolution: {integrity: sha512-I6fiaX09Xivtk+THaMfAwnA3MVA5Big1WHF1Dfx9hFuvNIWpXnorlkzhcQf6ehrqQiiZECRt1poOAkPmer3ruw==}
+ imurmurhash@0.1.4:
+ resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
+ engines: {node: '>=0.8.19'}
+
indent-string@4.0.0:
resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==}
engines: {node: '>=8'}
@@ -5101,6 +5265,9 @@ packages:
resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==}
engines: {node: '>=12'}
+ infer-owner@1.0.4:
+ resolution: {integrity: sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==}
+
inflation@2.1.0:
resolution: {integrity: sha512-t54PPJHG1Pp7VQvxyVCJ9mBbjG3Hqryges9bXoOO6GExCPa+//i/d5GSuFtpx3ALLd7lgIAur6zrIlBQyJuMlQ==}
engines: {node: '>= 0.8.0'}
@@ -5139,6 +5306,10 @@ packages:
resolution: {integrity: sha512-2YZsvl7jopIa1gaePkeMtd9rAcSjOOjPtpcLlOeusyO+XH2SK5ZcT+UCrElPP+WVIInh2TzeI4XW9ENaSLVVHA==}
engines: {node: '>=12.22.0'}
+ ip-address@9.0.5:
+ resolution: {integrity: sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==}
+ engines: {node: '>= 12'}
+
ip-regex@5.0.0:
resolution: {integrity: sha512-fOCG6lhoKKakwv+C6KdsOnGvgXnmgfmp0myi3bcNwj3qfwPAxRKWEuFhvEFF7ceYIz6+1jRZ+yguLFAmUNPEfw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -5211,6 +5382,9 @@ packages:
resolution: {integrity: sha512-4B4XA2HEIm/PY+OSpeMBXr8pGWBYbXuHgjMAqrwbLO3CPTCAd9ArEJzBUKGZtk9viY6+aSfadGnWyjY3ydYZkw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ is-lambda@1.0.1:
+ resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==}
+
is-number@7.0.0:
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
engines: {node: '>=0.12.0'}
@@ -5219,13 +5393,6 @@ packages:
resolution: {integrity: sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w==}
engines: {node: '>=8'}
- is-plain-obj@4.1.0:
- resolution: {integrity: sha512-+Pgi+vMuUNkJyExiMBt5IlFoMyKnr5zhJ4Uspz58WOhBF5QoIZkFyNHIbBAtHwzVAgk5RtndVNsDRN61/mmDqg==}
- engines: {node: '>=12'}
-
- is-promise@2.2.2:
- resolution: {integrity: sha512-+lP4/6lKUBfQjZ2pdxThZvLUAafmZb8OAxFb8XXtiQmS35INgr85hdOGoEs124ez1FCnZJt6jau/T+alh58QFQ==}
-
is-stream@3.0.0:
resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
@@ -5252,10 +5419,16 @@ packages:
resolution: {integrity: sha512-2yTgeWTWzMWkHu6Jp9NKgePDaYHbntiwvYuuJLbbN9vl7DC9DvXKOB2BC3ZZ92D3cvV/aflH0osDfwpHepQ53w==}
hasBin: true
+ jose@5.9.6:
+ resolution: {integrity: sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==}
+
joycon@3.1.1:
resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==}
engines: {node: '>=10'}
+ js-base64@3.7.7:
+ resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==}
+
js-beautify@1.15.1:
resolution: {integrity: sha512-ESjNzSlt/sWE8sciZH8kBF8BPlwXPwhR6pWKAw8bw4Bwj+iZcnKW6ONWUutJ7eObuBZQpiIb8S7OYspWrKt7rA==}
engines: {node: '>=14'}
@@ -5278,13 +5451,12 @@ packages:
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
hasBin: true
+ jsbn@1.1.0:
+ resolution: {integrity: sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==}
+
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
- json-diff@0.9.0:
- resolution: {integrity: sha512-cVnggDrVkAAA3OvFfHpFEhOnmcsUpleEKq4d4O8sQWWSH40MBrWstKigVB1kGrgLWzuom+7rRdaCsnBD6VyObQ==}
- hasBin: true
-
json-parse-even-better-errors@2.3.1:
resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==}
@@ -5361,6 +5533,10 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ kysely@0.27.5:
+ resolution: {integrity: sha512-s7hZHcQeSNKpzCkHRm8yA+0JPLjncSWnjb+2TIElwS2JAqYr+Kv3Ess+9KFfJS0C1xcQ1i9NkNHpWwCYpHMWsA==}
+ engines: {node: '>=14.0.0'}
+
leac@0.6.0:
resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
@@ -5508,9 +5684,6 @@ packages:
lodash.startcase@4.4.0:
resolution: {integrity: sha512-+WKqsK294HMSc2jEbNgpHpd0JfIBhp7rEV4aqXWqFr6AlXov+SlcgB1Fv01y2kGe3Gc8nMW7VA0SrGuSkRfIEg==}
- lodash.throttle@4.1.1:
- resolution: {integrity: sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==}
-
lodash.uniq@4.5.0:
resolution: {integrity: sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==}
@@ -5547,8 +5720,9 @@ packages:
lru-cache@10.4.3:
resolution: {integrity: sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==}
- lru-queue@0.1.0:
- resolution: {integrity: sha512-BpdYkt9EvGl8OfWHDQPISVpcl5xZthb+XPsbELj5AQXxIC8IriDZIQYjBJPEm5rS420sjZ0TLEzRcq5KdBhYrQ==}
+ lru-cache@6.0.0:
+ resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==}
+ engines: {node: '>=10'}
lucia@3.2.0:
resolution: {integrity: sha512-eXMxXwk6hqtjRTj4W/x3EnTUtAztLPm0p2N2TEBMDEbakDLXiYnDQ9z/qahjPdPdhPguQc+vwO0/88zIWxlpuw==}
@@ -5569,6 +5743,13 @@ packages:
resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==}
engines: {node: '>=8'}
+ make-error@1.3.6:
+ resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+
+ make-fetch-happen@9.1.0:
+ resolution: {integrity: sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==}
+ engines: {node: '>= 10'}
+
marked@7.0.4:
resolution: {integrity: sha512-t8eP0dXRJMtMvBojtkcsA7n48BkauktUKzfkPSCq85ZMTJ0v76Rke4DYz01omYpPTUh4p/f7HePgRo3ebG8+QQ==}
engines: {node: '>= 16'}
@@ -5618,10 +5799,6 @@ packages:
resolution: {integrity: sha512-+6kz90/YQoZuHvg3rn1CGPMZfEMaU5xe7xIavZMNiom2RNesiI8S37p9O9n+PlIUnUgretjLdM6HnqpZYl3X2g==}
engines: {node: '>= 4.0.0'}
- memoizee@0.4.17:
- resolution: {integrity: sha512-DGqD7Hjpi/1or4F/aYAspXKNm5Yili0QDAFAY4QYvpqpgiY6+1jOfqpmByzjxbWd/T9mChbCArXAbDAsTm5oXA==}
- engines: {node: '>=0.12'}
-
meow@12.1.1:
resolution: {integrity: sha512-BhXM0Au22RwUneMPwSCnyhTOizdWoIEPU9sp0Aqa1PnDMR5Wv2FGXYDjuzJEIX+Eo2Rb8xuYe5jrnm5QowQFkw==}
engines: {node: '>=16.10'}
@@ -5748,10 +5925,6 @@ packages:
minimatch@3.1.2:
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
- minimatch@5.1.6:
- resolution: {integrity: sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==}
- engines: {node: '>=10'}
-
minimatch@7.4.6:
resolution: {integrity: sha512-sBz8G/YjVniEz6lKPNpKxXwazJe4c19fEfV2GDMX6AjFz+MX9uDWIZW8XreVhkFW3fkIdTv/gxWr/Kks5FFAVw==}
engines: {node: '>=10'}
@@ -5767,6 +5940,26 @@ packages:
minimist@1.2.8:
resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==}
+ minipass-collect@1.0.2:
+ resolution: {integrity: sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==}
+ engines: {node: '>= 8'}
+
+ minipass-fetch@1.4.1:
+ resolution: {integrity: sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==}
+ engines: {node: '>=8'}
+
+ minipass-flush@1.0.5:
+ resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==}
+ engines: {node: '>= 8'}
+
+ minipass-pipeline@1.2.4:
+ resolution: {integrity: sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==}
+ engines: {node: '>=8'}
+
+ minipass-sized@1.0.3:
+ resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==}
+ engines: {node: '>=8'}
+
minipass@3.3.6:
resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==}
engines: {node: '>=8'}
@@ -5822,10 +6015,9 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
- nanoid@3.3.8:
- resolution: {integrity: sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==}
- engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
- hasBin: true
+ nanostores@0.11.3:
+ resolution: {integrity: sha512-TUes3xKIX33re4QzdxwZ6tdbodjmn3tWXCEc1uokiEmo14sI1EaGYNs2k3bU2pyyGNmBqFGAVl6jAGWd06AVIg==}
+ engines: {node: ^18.0.0 || >=20.0.0}
napi-build-utils@1.0.2:
resolution: {integrity: sha512-ONmRUqK7zj7DWX0D9ADe03wbwOBZxNAfF20PlGfCWQcD3+/MakShIHrMqx9YwPTfxDdF1zLeL+RGZiR9kGMLdg==}
@@ -5857,9 +6049,6 @@ packages:
react: '*'
react-dom: '*'
- next-tick@1.1.0:
- resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==}
-
next@15.0.1:
resolution: {integrity: sha512-PSkFkr/w7UnFWm+EP8y/QpHrJXMqpZzAXpergB/EqLPOh4SGPJXv1wj4mslr2hUZBAS9pX7/9YLIdxTv6fwytw==}
engines: {node: '>=18.18.0'}
@@ -5891,6 +6080,9 @@ packages:
node-addon-api@5.1.0:
resolution: {integrity: sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==}
+ node-addon-api@7.1.1:
+ resolution: {integrity: sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==}
+
node-domexception@1.0.0:
resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==}
engines: {node: '>=10.5.0'}
@@ -5915,6 +6107,11 @@ packages:
resolution: {integrity: sha512-s+w+rBWnpTMwSFbaE0UXsRlg7hU4FjekKU4eyAih5T8nJuNZT1nNsskXpxmeqSK9UzkBl6UgRlnKc8hz8IEqOw==}
hasBin: true
+ node-gyp@8.4.1:
+ resolution: {integrity: sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==}
+ engines: {node: '>= 10.12.0'}
+ hasBin: true
+
node-mocks-http@1.15.0:
resolution: {integrity: sha512-3orGBAxXrnwz3ixU8AZpa0x8srAvVSHvbWanAqd5F0zVCVA2QstxaVcTSarFcjz4+pFSnR1zm28MsV83s/BtmA==}
engines: {node: '>=14'}
@@ -5966,6 +6163,11 @@ packages:
resolution: {integrity: sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==}
deprecated: This package is no longer supported.
+ npmlog@6.0.2:
+ resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==}
+ engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
+ deprecated: This package is no longer supported.
+
object-assign@4.1.1:
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
engines: {node: '>=0.10.0'}
@@ -6050,6 +6252,10 @@ packages:
resolution: {integrity: sha512-wPrq66Llhl7/4AGC6I+cqxT07LhXvWL08LNXz1fENOw0Ap4sRZZ/gZpTTJ5jpurzzzfS2W/Ge9BY3LgLjCShcw==}
engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+ p-map@4.0.0:
+ resolution: {integrity: sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==}
+ engines: {node: '>=10'}
+
p-try@2.2.0:
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
engines: {node: '>=6'}
@@ -6247,6 +6453,18 @@ packages:
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
engines: {node: '>= 0.6.0'}
+ promise-inflight@1.0.1:
+ resolution: {integrity: sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==}
+ peerDependencies:
+ bluebird: '*'
+ peerDependenciesMeta:
+ bluebird:
+ optional: true
+
+ promise-retry@2.0.1:
+ resolution: {integrity: sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==}
+ engines: {node: '>=10'}
+
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
@@ -6273,6 +6491,13 @@ packages:
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
engines: {node: '>=6'}
+ pvtsutils@1.3.6:
+ resolution: {integrity: sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==}
+
+ pvutils@1.1.3:
+ resolution: {integrity: sha512-pMpnA0qRdFp32b1sJl1wOJNxZLQ2cbQx+k6tjNtZ8CpvVhNqEPRgivZ2WOUev2YMajecdH7ctUPDvEe87nariQ==}
+ engines: {node: '>=6.0.0'}
+
qrcode@1.5.4:
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
engines: {node: '>=10.13.0'}
@@ -6341,6 +6566,12 @@ packages:
peerDependencies:
react: ^15.3.0 || 16 || 17 || 18
+ react-day-picker@8.10.1:
+ resolution: {integrity: sha512-TMx7fNbhLk15eqcMt+7Z7S2KF7mfTId/XJDjKE8f+IUcFn0l08/kI4FiYTL/0yuOLmEcbR4Fwe3GJf/NiiMnPA==}
+ peerDependencies:
+ date-fns: ^2.28.0 || ^3.0.0
+ react: ^16.8.0 || ^17.0.0 || ^18.0.0
+
react-debounce-input@3.3.0:
resolution: {integrity: sha512-VEqkvs8JvY/IIZvh71Z0TC+mdbxERvYF33RcebnodlsUZ8RSgyKe2VWaHXv4+/8aoOgXLxWrdsYs2hDhcwbUgA==}
peerDependencies:
@@ -6599,6 +6830,10 @@ packages:
resolution: {integrity: sha512-M0b3YWQs7R3Z917WRQy1HHA7Ba7D8hvZg6UE5mLykJxQVE2ju0IXbGlaHPPlkY+WN7wFP+wUMXmBFA0aV6vYGQ==}
engines: {node: '>=4'}
+ retry@0.12.0:
+ resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
+ engines: {node: '>= 4'}
+
reusify@1.0.4:
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
@@ -6620,6 +6855,9 @@ packages:
resolution: {integrity: sha512-cfmm3tqdnbuYw2FBmRTPBDaohYEbMJ3211T35o6eZdr4d7v69+ZeK1Av84Br7FLj2dlzyeZSbN6qTuXXE6dawQ==}
engines: {node: '>=14.0'}
+ rou3@0.5.1:
+ resolution: {integrity: sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==}
+
run-parallel@1.2.0:
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
@@ -6677,6 +6915,9 @@ packages:
set-blocking@2.0.0:
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
+ set-cookie-parser@2.7.1:
+ resolution: {integrity: sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==}
+
set-function-length@1.2.2:
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
engines: {node: '>= 0.4'}
@@ -6730,9 +6971,6 @@ packages:
simple-swizzle@0.2.2:
resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==}
- sisteransi@1.0.5:
- resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
-
slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
@@ -6753,6 +6991,18 @@ packages:
resolution: {integrity: sha512-h+z7HKHYXj6wJU+AnS/+IH8Uh9fdcX1Lrhg1/VMdf9PwoBQXFcXiAdsy2tSK0P6gKwJLXp02r90ahUCqHk9rrw==}
engines: {node: '>=8.0.0'}
+ smart-buffer@4.2.0:
+ resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==}
+ engines: {node: '>= 6.0.0', npm: '>= 3.0.0'}
+
+ socks-proxy-agent@6.2.1:
+ resolution: {integrity: sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==}
+ engines: {node: '>= 10'}
+
+ socks@2.8.3:
+ resolution: {integrity: sha512-l5x7VUUWbjVFbafGLxPWkYsHIhEvmF85tbIeFZWc8ZPtoMyybuEhL7Jye/ooC4/d48FgOjSJXgsF/AJPYCW8Zw==}
+ engines: {node: '>= 10.0.0', npm: '>= 3.0.0'}
+
sonic-boom@4.1.0:
resolution: {integrity: sha512-NGipjjRicyJJ03rPiZCJYjwlsuP2d1/5QUviozRXC7S3WdVWNK5e3Ojieb9CCyfhq2UC+3+SRd9nG3I2lPRvUw==}
@@ -6792,10 +7042,20 @@ packages:
sprintf-js@1.0.3:
resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==}
+ sprintf-js@1.1.3:
+ resolution: {integrity: sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==}
+
+ sqlite3@5.1.7:
+ resolution: {integrity: sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==}
+
ssh2@1.15.0:
resolution: {integrity: sha512-C0PHgX4h6lBxYx7hcXwu3QWdh4tg6tZZsTfXcdvc5caW/EMxaB4H9dWsl7qk+F7LAW762hp8VbXOX7x4xUYvEw==}
engines: {node: '>=10.16.0'}
+ ssri@8.0.1:
+ resolution: {integrity: sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==}
+ engines: {node: '>= 8'}
+
stackback@0.0.2:
resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==}
@@ -7002,10 +7262,6 @@ packages:
through@2.3.8:
resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==}
- timers-ext@0.1.8:
- resolution: {integrity: sha512-wFH7+SEAcKfJpfLPkrgMPvvwnEtj8W4IurvEyrKsDleXnKLCDw71w8jltvfLa8Rm4qQxxT4jmDBYbJG/z7qoww==}
- engines: {node: '>=0.12'}
-
tiny-invariant@1.3.3:
resolution: {integrity: sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==}
@@ -7064,6 +7320,20 @@ packages:
ts-mixer@6.0.4:
resolution: {integrity: sha512-ufKpbmrugz5Aou4wcr5Wc1UUFWOLhq+Fm6qa6P0w0K5Qw2yhaUoiWszhCVuNQyNwrlGiscHOmqYoAox1PtvgjA==}
+ ts-node@10.9.2:
+ resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
+ hasBin: true
+ peerDependencies:
+ '@swc/core': '>=1.2.50'
+ '@swc/wasm': '>=1.2.50'
+ '@types/node': '*'
+ typescript: '>=2.7'
+ peerDependenciesMeta:
+ '@swc/core':
+ optional: true
+ '@swc/wasm':
+ optional: true
+
ts-toolbelt@9.6.0:
resolution: {integrity: sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w==}
@@ -7084,6 +7354,9 @@ packages:
tslib@2.6.3:
resolution: {integrity: sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==}
+ tslib@2.8.1:
+ resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
+
tsx@4.16.2:
resolution: {integrity: sha512-C1uWweJDgdtX2x600HjaFaucXTilT7tgUZHbOE4+ypskZ1OP8CRCSDkCxG6Vya9EwaFIVagWwpaVAn5wzypaqQ==}
engines: {node: '>=18.0.0'}
@@ -7111,9 +7384,6 @@ packages:
resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==}
engines: {node: '>= 0.6'}
- type@2.7.3:
- resolution: {integrity: sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==}
-
types-ramda@0.30.1:
resolution: {integrity: sha512-1HTsf5/QVRmLzcGfldPFvkVsAdi1db1BBKzi7iW3KBUlOICg/nKnFS+jGqDJS3YD8VsWbAh7JiHeBvbsw8RPxA==}
@@ -7122,6 +7392,11 @@ packages:
engines: {node: '>=14.17'}
hasBin: true
+ typescript@5.7.2:
+ resolution: {integrity: sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==}
+ engines: {node: '>=14.17'}
+ hasBin: true
+
ufo@1.5.4:
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
@@ -7142,23 +7417,11 @@ packages:
resolution: {integrity: sha512-lRfVq8fE8gz6QMBuDM6a+LO3IAzTi05H6gCVaUpir2E1Rwpo4ZUog45KpNXKC/Mn3Yb9UDuHumeFTo9iV/D9FQ==}
engines: {node: '>=18'}
- unified@11.0.5:
- resolution: {integrity: sha512-xKvGhPWw3k84Qjh8bI3ZeJjqnyadK+GEFtazSfZv/rKeTkTjOJho6mFqh2SM96iIcZokxiOpg78GazTSg8+KHA==}
+ unique-filename@1.1.1:
+ resolution: {integrity: sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==}
- unist-util-is@6.0.0:
- resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==}
-
- unist-util-position@5.0.0:
- resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==}
-
- unist-util-stringify-position@4.0.0:
- resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==}
-
- unist-util-visit-parents@6.0.1:
- resolution: {integrity: sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==}
-
- unist-util-visit@5.0.0:
- resolution: {integrity: sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==}
+ unique-slug@2.0.2:
+ resolution: {integrity: sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==}
universal-github-app-jwt@1.1.2:
resolution: {integrity: sha512-t1iB2FmLFE+yyJY9+3wMx0ejB+MQpEVkH0gQv7dR6FZyltyq+ZZO0uDpbopxhrZ3SLEO4dCEkIujOMldEQ2iOA==}
@@ -7228,11 +7491,16 @@ packages:
resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==}
hasBin: true
- vfile-message@4.0.2:
- resolution: {integrity: sha512-jRDZ1IMLttGj41KcZvlrYAaI3CfqpLpfpf+Mfig13viT6NKvRzWZ+lXz0Y5D60w6uJIBAOGq9mSHf0gktF0duw==}
+ v8-compile-cache-lib@3.0.1:
+ resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
- vfile@6.0.3:
- resolution: {integrity: sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q==}
+ valibot@1.0.0-beta.15:
+ resolution: {integrity: sha512-BKy8XosZkDHWmYC+cJG74LBzP++Gfntwi33pP3D3RKztz2XV9jmFWnkOi21GoqARP8wAWARwhV6eTr1JcWzjGw==}
+ peerDependencies:
+ typescript: '>=5'
+ peerDependenciesMeta:
+ typescript:
+ optional: true
victory-vendor@36.9.2:
resolution: {integrity: sha512-PnpQQMuxlwYdocC8fIJqVXvkeViHYzotI+NJrCuav0ZYFoq912ZHBk3mCeuj+5/VpodOjPe1z0Fk2ihgzlXqjQ==}
@@ -7361,9 +7629,6 @@ packages:
resolution: {integrity: sha512-o0cyEG0e8GPzT4iGHphIOh0cJOV8fivsXxddQasHPHfoZf1ZexrfeA21w2NaEN1RHE+fXlfISmOE8R9N3u3Qig==}
engines: {node: '>=12'}
- wordwrap@1.0.0:
- resolution: {integrity: sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==}
-
wrap-ansi@6.2.0:
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
engines: {node: '>=8'}
@@ -7446,6 +7711,10 @@ packages:
resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==}
engines: {node: '>=12'}
+ yn@3.1.1:
+ resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
+ engines: {node: '>=6'}
+
yocto-queue@1.1.1:
resolution: {integrity: sha512-b4JR1PFR10y1mKjhHY9LaGo6tmrgjit7hxVIeAmyMw3jegXR4dhYqLaQF5zMXZxY7tLpMyJeLjr1C4rLmkVe8g==}
engines: {node: '>=12.20'}
@@ -7471,8 +7740,8 @@ packages:
zod@3.23.8:
resolution: {integrity: sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==}
- zwitch@2.0.4:
- resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
+ zod@3.24.1:
+ resolution: {integrity: sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==}
snapshots:
@@ -7591,6 +7860,12 @@ snapshots:
'@balena/dockerignore@1.0.2': {}
+ '@better-auth/utils@0.2.3':
+ dependencies:
+ uncrypto: 0.1.3
+
+ '@better-fetch/fetch@1.1.15': {}
+
'@biomejs/biome@1.9.4':
optionalDependencies:
'@biomejs/cli-darwin-arm64': 1.9.4
@@ -7698,11 +7973,11 @@ snapshots:
style-mod: 4.1.2
w3c-keyname: 2.2.8
- '@commitlint/cli@19.3.0(@types/node@18.19.42)(typescript@5.5.3)':
+ '@commitlint/cli@19.3.0(@types/node@18.19.42)(typescript@5.7.2)':
dependencies:
'@commitlint/format': 19.3.0
'@commitlint/lint': 19.2.2
- '@commitlint/load': 19.2.0(@types/node@18.19.42)(typescript@5.5.3)
+ '@commitlint/load': 19.2.0(@types/node@18.19.42)(typescript@5.7.2)
'@commitlint/read': 19.2.1
'@commitlint/types': 19.0.3
execa: 8.0.1
@@ -7749,15 +8024,15 @@ snapshots:
'@commitlint/rules': 19.0.3
'@commitlint/types': 19.0.3
- '@commitlint/load@19.2.0(@types/node@18.19.42)(typescript@5.5.3)':
+ '@commitlint/load@19.2.0(@types/node@18.19.42)(typescript@5.7.2)':
dependencies:
'@commitlint/config-validator': 19.0.3
'@commitlint/execute-rule': 19.0.0
'@commitlint/resolve-extends': 19.1.0
'@commitlint/types': 19.0.3
chalk: 5.3.0
- cosmiconfig: 9.0.0(typescript@5.5.3)
- cosmiconfig-typescript-loader: 5.0.0(@types/node@18.19.42)(cosmiconfig@9.0.0(typescript@5.5.3))(typescript@5.5.3)
+ cosmiconfig: 9.0.0(typescript@5.7.2)
+ cosmiconfig-typescript-loader: 5.0.0(@types/node@18.19.42)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2)
lodash.isplainobject: 4.0.6
lodash.merge: 4.6.2
lodash.uniq: 4.5.0
@@ -7809,6 +8084,11 @@ snapshots:
'@types/conventional-commits-parser': 5.0.0
chalk: 5.3.0
+ '@cspotcode/source-map-support@0.8.1':
+ dependencies:
+ '@jridgewell/trace-mapping': 0.3.9
+ optional: true
+
'@dokploy/trpc-openapi@0.0.4(@trpc/server@10.45.2)(zod@3.23.8)':
dependencies:
'@trpc/server': 10.45.2
@@ -7822,6 +8102,8 @@ snapshots:
transitivePeerDependencies:
- uWebSockets.js
+ '@drizzle-team/brocli@0.10.2': {}
+
'@emnapi/core@0.45.0':
dependencies:
tslib: 2.6.3
@@ -8145,8 +8427,13 @@ snapshots:
'@floating-ui/utils@0.2.5': {}
+ '@gar/promisify@1.1.3':
+ optional: true
+
'@hapi/bourne@3.0.0': {}
+ '@hexagon/base64@1.1.28': {}
+
'@hono/node-server@1.12.1': {}
'@hono/zod-validator@0.3.0(hono@4.5.8)(zod@3.23.8)':
@@ -8270,6 +8557,12 @@ snapshots:
'@jridgewell/resolve-uri': 3.1.2
'@jridgewell/sourcemap-codec': 1.5.0
+ '@jridgewell/trace-mapping@0.3.9':
+ dependencies:
+ '@jridgewell/resolve-uri': 3.1.2
+ '@jridgewell/sourcemap-codec': 1.5.0
+ optional: true
+
'@jsonjoy.com/base64@1.1.2(tslib@2.6.3)':
dependencies:
tslib: 2.6.3
@@ -8290,6 +8583,8 @@ snapshots:
'@leichtgewicht/ip-codec@2.0.5': {}
+ '@levischuck/tiny-cbor@0.2.11': {}
+
'@lezer/common@1.2.1': {}
'@lezer/highlight@1.2.0':
@@ -8316,12 +8611,12 @@ snapshots:
dependencies:
lucia: 3.2.0
- '@mapbox/node-pre-gyp@1.0.11':
+ '@mapbox/node-pre-gyp@1.0.11(encoding@0.1.13)':
dependencies:
detect-libc: 2.0.3
https-proxy-agent: 5.0.1
make-dir: 3.1.0
- node-fetch: 2.7.0
+ node-fetch: 2.7.0(encoding@0.1.13)
nopt: 5.0.0
npmlog: 5.0.1
rimraf: 3.0.2
@@ -8380,8 +8675,12 @@ snapshots:
'@next/swc-win32-x64-msvc@15.0.1':
optional: true
+ '@noble/ciphers@0.6.0': {}
+
'@noble/hashes@1.5.0': {}
+ '@noble/hashes@1.7.1': {}
+
'@node-rs/argon2-android-arm-eabi@1.7.0':
optional: true
@@ -8522,6 +8821,18 @@ snapshots:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.17.1
+ '@npmcli/fs@1.1.1':
+ dependencies:
+ '@gar/promisify': 1.1.3
+ semver: 7.6.3
+ optional: true
+
+ '@npmcli/move-file@1.1.2':
+ dependencies:
+ mkdirp: 1.0.4
+ rimraf: 3.0.2
+ optional: true
+
'@octokit/app@14.1.0':
dependencies:
'@octokit/auth-app': 6.1.1
@@ -8698,7 +9009,51 @@ snapshots:
'@one-ini/wasm@0.1.1': {}
- '@opentelemetry/api@1.9.0': {}
+ '@oslojs/asn1@1.0.0':
+ dependencies:
+ '@oslojs/binary': 1.0.0
+
+ '@oslojs/binary@1.0.0': {}
+
+ '@oslojs/crypto@1.0.1':
+ dependencies:
+ '@oslojs/asn1': 1.0.0
+ '@oslojs/binary': 1.0.0
+
+ '@oslojs/encoding@1.1.0': {}
+
+ '@peculiar/asn1-android@2.3.15':
+ dependencies:
+ '@peculiar/asn1-schema': 2.3.15
+ asn1js: 3.0.5
+ tslib: 2.8.1
+
+ '@peculiar/asn1-ecc@2.3.15':
+ dependencies:
+ '@peculiar/asn1-schema': 2.3.15
+ '@peculiar/asn1-x509': 2.3.15
+ asn1js: 3.0.5
+ tslib: 2.8.1
+
+ '@peculiar/asn1-rsa@2.3.15':
+ dependencies:
+ '@peculiar/asn1-schema': 2.3.15
+ '@peculiar/asn1-x509': 2.3.15
+ asn1js: 3.0.5
+ tslib: 2.8.1
+
+ '@peculiar/asn1-schema@2.3.15':
+ dependencies:
+ asn1js: 3.0.5
+ pvtsutils: 1.3.6
+ tslib: 2.8.1
+
+ '@peculiar/asn1-x509@2.3.15':
+ dependencies:
+ '@peculiar/asn1-schema': 2.3.15
+ asn1js: 3.0.5
+ pvtsutils: 1.3.6
+ tslib: 2.8.1
'@pkgjs/parseargs@0.11.0':
optional: true
@@ -9693,6 +10048,18 @@ snapshots:
domhandler: 5.0.3
selderee: 0.11.0
+ '@simplewebauthn/browser@13.1.0': {}
+
+ '@simplewebauthn/server@13.1.1':
+ dependencies:
+ '@hexagon/base64': 1.1.28
+ '@levischuck/tiny-cbor': 0.2.11
+ '@peculiar/asn1-android': 2.3.15
+ '@peculiar/asn1-ecc': 2.3.15
+ '@peculiar/asn1-rsa': 2.3.15
+ '@peculiar/asn1-schema': 2.3.15
+ '@peculiar/asn1-x509': 2.3.15
+
'@sinclair/typebox@0.27.8': {}
'@sindresorhus/is@5.6.0': {}
@@ -10074,6 +10441,9 @@ snapshots:
'@tanstack/table-core@8.19.3': {}
+ '@tootallnate/once@1.1.2':
+ optional: true
+
'@trpc/client@10.45.2(@trpc/server@10.45.2)':
dependencies:
'@trpc/server': 10.45.2
@@ -10098,6 +10468,18 @@ snapshots:
'@trpc/server@10.45.2': {}
+ '@tsconfig/node10@1.0.11':
+ optional: true
+
+ '@tsconfig/node12@1.0.11':
+ optional: true
+
+ '@tsconfig/node14@1.0.3':
+ optional: true
+
+ '@tsconfig/node16@1.0.4':
+ optional: true
+
'@tybys/wasm-util@0.8.3':
dependencies:
tslib: 2.6.3
@@ -10462,6 +10844,11 @@ snapshots:
dependencies:
'@xterm/xterm': 5.5.0
+ '@xterm/addon-clipboard@0.1.0(@xterm/xterm@5.5.0)':
+ dependencies:
+ '@xterm/xterm': 5.5.0
+ js-base64: 3.7.7
+
'@xterm/xterm@5.5.0': {}
'@xtuc/ieee754@1.2.0': {}
@@ -10504,6 +10891,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ agentkeepalive@4.6.0:
+ dependencies:
+ humanize-ms: 1.2.1
+ optional: true
+
aggregate-error@3.1.0:
dependencies:
clean-stack: 2.2.0
@@ -10594,6 +10986,15 @@ snapshots:
delegates: 1.0.0
readable-stream: 3.6.2
+ are-we-there-yet@3.0.1:
+ dependencies:
+ delegates: 1.0.0
+ readable-stream: 3.6.2
+ optional: true
+
+ arg@4.1.3:
+ optional: true
+
arg@5.0.2: {}
argparse@1.0.10:
@@ -10614,6 +11015,12 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
+ asn1js@3.0.5:
+ dependencies:
+ pvtsutils: 1.3.6
+ pvutils: 1.1.3
+ tslib: 2.8.1
+
assertion-error@1.1.0: {}
async-await-queue@2.1.4: {}
@@ -10654,9 +11061,9 @@ snapshots:
dependencies:
tweetnacl: 0.14.5
- bcrypt@5.1.1:
+ bcrypt@5.1.1(encoding@0.1.13):
dependencies:
- '@mapbox/node-pre-gyp': 1.0.11
+ '@mapbox/node-pre-gyp': 1.0.11(encoding@0.1.13)
node-addon-api: 5.1.0
transitivePeerDependencies:
- encoding
@@ -10664,8 +11071,38 @@ snapshots:
before-after-hook@2.2.3: {}
+ better-auth@1.2.0(typescript@5.5.3):
+ dependencies:
+ '@better-auth/utils': 0.2.3
+ '@better-fetch/fetch': 1.1.15
+ '@noble/ciphers': 0.6.0
+ '@noble/hashes': 1.7.1
+ '@simplewebauthn/browser': 13.1.0
+ '@simplewebauthn/server': 13.1.1
+ better-call: 1.0.3
+ defu: 6.1.4
+ jose: 5.9.6
+ kysely: 0.27.5
+ nanostores: 0.11.3
+ valibot: 1.0.0-beta.15(typescript@5.5.3)
+ zod: 3.24.1
+ transitivePeerDependencies:
+ - typescript
+
+ better-call@1.0.3:
+ dependencies:
+ '@better-fetch/fetch': 1.1.15
+ rou3: 0.5.1
+ set-cookie-parser: 2.7.1
+ uncrypto: 0.1.3
+
binary-extensions@2.3.0: {}
+ bindings@1.5.0:
+ dependencies:
+ file-uri-to-path: 1.0.0
+ optional: true
+
bl@4.1.0:
dependencies:
buffer: 5.7.1
@@ -10752,6 +11189,30 @@ snapshots:
cac@6.7.14: {}
+ cacache@15.3.0:
+ dependencies:
+ '@npmcli/fs': 1.1.1
+ '@npmcli/move-file': 1.1.2
+ chownr: 2.0.0
+ fs-minipass: 2.1.0
+ glob: 7.2.3
+ infer-owner: 1.0.4
+ lru-cache: 6.0.0
+ minipass: 3.3.6
+ minipass-collect: 1.0.2
+ minipass-flush: 1.0.5
+ minipass-pipeline: 1.2.4
+ mkdirp: 1.0.4
+ p-map: 4.0.0
+ promise-inflight: 1.0.1
+ rimraf: 3.0.2
+ ssri: 8.0.1
+ tar: 6.2.1
+ unique-filename: 1.1.1
+ transitivePeerDependencies:
+ - bluebird
+ optional: true
+
cacheable-lookup@7.0.0: {}
cacheable-request@10.2.14:
@@ -10852,14 +11313,6 @@ snapshots:
cli-boxes@3.0.0: {}
- cli-color@2.0.4:
- dependencies:
- d: 1.0.2
- es5-ext: 0.10.64
- es6-iterator: 2.0.3
- memoizee: 0.4.17
- timers-ext: 0.1.8
-
cli-cursor@5.0.0:
dependencies:
restore-cursor: 5.1.0
@@ -11026,21 +11479,21 @@ snapshots:
core-js@3.39.0: {}
- cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.42)(cosmiconfig@9.0.0(typescript@5.5.3))(typescript@5.5.3):
+ cosmiconfig-typescript-loader@5.0.0(@types/node@18.19.42)(cosmiconfig@9.0.0(typescript@5.7.2))(typescript@5.7.2):
dependencies:
'@types/node': 18.19.42
- cosmiconfig: 9.0.0(typescript@5.5.3)
+ cosmiconfig: 9.0.0(typescript@5.7.2)
jiti: 1.21.6
- typescript: 5.5.3
+ typescript: 5.7.2
- cosmiconfig@9.0.0(typescript@5.5.3):
+ cosmiconfig@9.0.0(typescript@5.7.2):
dependencies:
env-paths: 2.2.1
import-fresh: 3.3.0
js-yaml: 4.1.0
parse-json: 5.2.0
optionalDependencies:
- typescript: 5.5.3
+ typescript: 5.7.2
cpu-features@0.0.10:
dependencies:
@@ -11048,6 +11501,9 @@ snapshots:
nan: 2.20.0
optional: true
+ create-require@1.1.1:
+ optional: true
+
crelt@1.0.6: {}
cron-parser@4.9.0:
@@ -11117,11 +11573,6 @@ snapshots:
d3-timer@3.0.1: {}
- d@1.0.2:
- dependencies:
- es5-ext: 0.10.64
- type: 2.7.3
-
dargs@8.1.0: {}
date-fns@3.6.0: {}
@@ -11196,9 +11647,8 @@ snapshots:
diff-sequences@29.6.3: {}
- difflib@0.2.4:
- dependencies:
- heap: 0.2.7
+ diff@4.0.2:
+ optional: true
dijkstrajs@1.0.3: {}
@@ -11266,34 +11716,31 @@ snapshots:
drange@1.1.1: {}
- dreamopt@0.8.0:
+ drizzle-dbml-generator@0.10.0(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)):
dependencies:
- wordwrap: 1.0.0
+ drizzle-orm: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
- drizzle-kit@0.21.4:
+ drizzle-kit@0.30.4:
dependencies:
+ '@drizzle-team/brocli': 0.10.2
'@esbuild-kit/esm-loader': 2.6.5
- commander: 9.5.0
- env-paths: 3.0.0
esbuild: 0.19.12
esbuild-register: 3.6.0(esbuild@0.19.12)
- glob: 8.1.0
- hanji: 0.0.5
- json-diff: 0.9.0
- zod: 3.23.8
transitivePeerDependencies:
- supports-color
- drizzle-orm@0.30.10(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0):
+ drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7):
optionalDependencies:
'@opentelemetry/api': 1.9.0
'@types/react': 18.3.5
+ kysely: 0.27.5
postgres: 3.4.4
react: 18.2.0
+ sqlite3: 5.1.7
- drizzle-zod@0.5.1(drizzle-orm@0.30.10(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0))(zod@3.23.8):
+ drizzle-zod@0.5.1(drizzle-orm@0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7))(zod@3.23.8):
dependencies:
- drizzle-orm: 0.30.10(@opentelemetry/api@1.9.0)(@types/react@18.3.5)(postgres@3.4.4)(react@18.2.0)
+ drizzle-orm: 0.39.1(@types/react@18.3.5)(kysely@0.27.5)(postgres@3.4.4)(react@18.2.0)(sqlite3@5.1.7)
zod: 3.23.8
eastasianwidth@0.2.0: {}
@@ -11317,6 +11764,11 @@ snapshots:
emoji-regex@9.2.2: {}
+ encoding@0.1.13:
+ dependencies:
+ iconv-lite: 0.6.3
+ optional: true
+
end-of-stream@1.4.4:
dependencies:
once: 1.4.0
@@ -11330,10 +11782,11 @@ snapshots:
env-paths@2.2.1: {}
- env-paths@3.0.0: {}
-
environment@1.1.0: {}
+ err-code@2.0.3:
+ optional: true
+
error-ex@1.3.2:
dependencies:
is-arrayish: 0.2.1
@@ -11346,31 +11799,6 @@ snapshots:
es-module-lexer@1.5.4: {}
- es5-ext@0.10.64:
- dependencies:
- es6-iterator: 2.0.3
- es6-symbol: 3.1.4
- esniff: 2.0.1
- next-tick: 1.1.0
-
- es6-iterator@2.0.3:
- dependencies:
- d: 1.0.2
- es5-ext: 0.10.64
- es6-symbol: 3.1.4
-
- es6-symbol@3.1.4:
- dependencies:
- d: 1.0.2
- ext: 1.7.0
-
- es6-weak-map@2.0.3:
- dependencies:
- d: 1.0.2
- es5-ext: 0.10.64
- es6-iterator: 2.0.3
- es6-symbol: 3.1.4
-
esbuild-plugin-alias@0.2.1: {}
esbuild-register@3.6.0(esbuild@0.19.12):
@@ -11496,13 +11924,6 @@ snapshots:
esrecurse: 4.3.0
estraverse: 4.3.0
- esniff@2.0.1:
- dependencies:
- d: 1.0.2
- es5-ext: 0.10.64
- event-emitter: 0.3.5
- type: 2.7.3
-
esrecurse@4.3.0:
dependencies:
estraverse: 5.3.0
@@ -11517,11 +11938,6 @@ snapshots:
dependencies:
'@types/estree': 1.0.5
- event-emitter@0.3.5:
- dependencies:
- d: 1.0.2
- es5-ext: 0.10.64
-
event-target-shim@5.0.1: {}
eventemitter3@4.0.7: {}
@@ -11547,12 +11963,6 @@ snapshots:
expand-template@2.0.3:
optional: true
- ext@1.7.0:
- dependencies:
- type: 2.7.3
-
- extend@3.0.2: {}
-
fancy-ansi@0.1.3:
dependencies:
escape-html: 1.0.3
@@ -11591,6 +12001,9 @@ snapshots:
dependencies:
format: 0.2.2
+ file-uri-to-path@1.0.0:
+ optional: true
+
fill-range@7.1.1:
dependencies:
to-regex-range: 5.0.1
@@ -11655,6 +12068,18 @@ snapshots:
strip-ansi: 6.0.1
wide-align: 1.1.5
+ gauge@4.0.4:
+ dependencies:
+ aproba: 2.0.0
+ color-support: 1.1.3
+ console-control-strings: 1.1.0
+ has-unicode: 2.0.1
+ signal-exit: 3.0.7
+ string-width: 4.2.3
+ strip-ansi: 6.0.1
+ wide-align: 1.1.5
+ optional: true
+
generic-pool@3.9.0: {}
get-caller-file@2.0.5: {}
@@ -11718,14 +12143,6 @@ snapshots:
once: 1.4.0
path-is-absolute: 1.0.1
- glob@8.1.0:
- dependencies:
- fs.realpath: 1.0.0
- inflight: 1.0.6
- inherits: 2.0.4
- minimatch: 5.1.6
- once: 1.4.0
-
global-directory@4.0.1:
dependencies:
ini: 4.1.1
@@ -11785,11 +12202,6 @@ snapshots:
transitivePeerDependencies:
- uWebSockets.js
- hanji@0.0.5:
- dependencies:
- lodash.throttle: 4.1.1
- sisteransi: 1.0.5
-
has-flag@3.0.0: {}
has-flag@4.0.0: {}
@@ -11842,8 +12254,6 @@ snapshots:
property-information: 5.6.0
space-separated-tokens: 1.1.5
- heap@0.2.7: {}
-
help-me@5.0.0: {}
hi-base32@0.5.1: {}
@@ -11887,6 +12297,15 @@ snapshots:
statuses: 2.0.1
toidentifier: 1.0.1
+ http-proxy-agent@4.0.1:
+ dependencies:
+ '@tootallnate/once': 1.1.2
+ agent-base: 6.0.2
+ debug: 4.3.7
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
http2-wrapper@2.2.1:
dependencies:
quick-lru: 5.1.1
@@ -11901,6 +12320,11 @@ snapshots:
human-signals@5.0.0: {}
+ humanize-ms@1.2.1:
+ dependencies:
+ ms: 2.1.3
+ optional: true
+
hyperdyperid@1.2.0: {}
hyphenate-style-name@1.1.0: {}
@@ -11915,6 +12339,11 @@ snapshots:
dependencies:
safer-buffer: 2.1.2
+ iconv-lite@0.6.3:
+ dependencies:
+ safer-buffer: 2.1.2
+ optional: true
+
ieee754@1.2.1: {}
ignore@5.3.1: {}
@@ -11928,10 +12357,16 @@ snapshots:
import-meta-resolve@4.1.0: {}
+ imurmurhash@0.1.4:
+ optional: true
+
indent-string@4.0.0: {}
indent-string@5.0.0: {}
+ infer-owner@1.0.4:
+ optional: true
+
inflation@2.1.0: {}
inflight@1.0.6:
@@ -11972,6 +12407,12 @@ snapshots:
transitivePeerDependencies:
- supports-color
+ ip-address@9.0.5:
+ dependencies:
+ jsbn: 1.1.0
+ sprintf-js: 1.1.3
+ optional: true
+
ip-regex@5.0.0: {}
iron-webcrypto@1.2.1: {}
@@ -12031,14 +12472,13 @@ snapshots:
dependencies:
ip-regex: 5.0.0
+ is-lambda@1.0.1:
+ optional: true
+
is-number@7.0.0: {}
is-obj@2.0.0: {}
- is-plain-obj@4.1.0: {}
-
- is-promise@2.2.2: {}
-
is-stream@3.0.0: {}
is-text-path@2.0.0:
@@ -12063,8 +12503,12 @@ snapshots:
jiti@1.21.6: {}
+ jose@5.9.6: {}
+
joycon@3.1.1: {}
+ js-base64@3.7.7: {}
+
js-beautify@1.15.1:
dependencies:
config-chain: 1.1.13
@@ -12085,13 +12529,10 @@ snapshots:
dependencies:
argparse: 2.0.1
- json-buffer@3.0.1: {}
+ jsbn@1.1.0:
+ optional: true
- json-diff@0.9.0:
- dependencies:
- cli-color: 2.0.4
- difflib: 0.2.4
- dreamopt: 0.8.0
+ json-buffer@3.0.1: {}
json-parse-even-better-errors@2.3.1: {}
@@ -12229,6 +12670,8 @@ snapshots:
dependencies:
json-buffer: 3.0.1
+ kysely@0.27.5: {}
+
leac@0.6.0: {}
lefthook-darwin-arm64@1.8.4:
@@ -12355,8 +12798,6 @@ snapshots:
lodash.startcase@4.4.0: {}
- lodash.throttle@4.1.1: {}
-
lodash.uniq@4.5.0: {}
lodash.upperfirst@4.3.1: {}
@@ -12392,9 +12833,10 @@ snapshots:
lru-cache@10.4.3: {}
- lru-queue@0.1.0:
+ lru-cache@6.0.0:
dependencies:
- es5-ext: 0.10.64
+ yallist: 4.0.0
+ optional: true
lucia@3.2.0:
dependencies:
@@ -12414,6 +12856,32 @@ snapshots:
dependencies:
semver: 6.3.1
+ make-error@1.3.6:
+ optional: true
+
+ make-fetch-happen@9.1.0:
+ dependencies:
+ agentkeepalive: 4.6.0
+ cacache: 15.3.0
+ http-cache-semantics: 4.1.1
+ http-proxy-agent: 4.0.1
+ https-proxy-agent: 5.0.1
+ is-lambda: 1.0.1
+ lru-cache: 6.0.0
+ minipass: 3.3.6
+ minipass-collect: 1.0.2
+ minipass-fetch: 1.4.1
+ minipass-flush: 1.0.5
+ minipass-pipeline: 1.2.4
+ negotiator: 0.6.3
+ promise-retry: 2.0.1
+ socks-proxy-agent: 6.2.1
+ ssri: 8.0.1
+ transitivePeerDependencies:
+ - bluebird
+ - supports-color
+ optional: true
+
marked@7.0.4: {}
md-to-react-email@5.0.2(react@18.2.0):
@@ -12529,17 +12997,6 @@ snapshots:
tree-dump: 1.0.2(tslib@2.6.3)
tslib: 2.6.3
- memoizee@0.4.17:
- dependencies:
- d: 1.0.2
- es5-ext: 0.10.64
- es6-weak-map: 2.0.3
- event-emitter: 0.3.5
- is-promise: 2.2.2
- lru-queue: 0.1.0
- next-tick: 1.1.0
- timers-ext: 0.1.8
-
meow@12.1.1: {}
merge-descriptors@1.0.3: {}
@@ -12714,10 +13171,6 @@ snapshots:
dependencies:
brace-expansion: 1.1.11
- minimatch@5.1.6:
- dependencies:
- brace-expansion: 2.0.1
-
minimatch@7.4.6:
dependencies:
brace-expansion: 2.0.1
@@ -12732,6 +13185,35 @@ snapshots:
minimist@1.2.8: {}
+ minipass-collect@1.0.2:
+ dependencies:
+ minipass: 3.3.6
+ optional: true
+
+ minipass-fetch@1.4.1:
+ dependencies:
+ minipass: 3.3.6
+ minipass-sized: 1.0.3
+ minizlib: 2.1.2
+ optionalDependencies:
+ encoding: 0.1.13
+ optional: true
+
+ minipass-flush@1.0.5:
+ dependencies:
+ minipass: 3.3.6
+ optional: true
+
+ minipass-pipeline@1.2.4:
+ dependencies:
+ minipass: 3.3.6
+ optional: true
+
+ minipass-sized@1.0.3:
+ dependencies:
+ minipass: 3.3.6
+ optional: true
+
minipass@3.3.6:
dependencies:
yallist: 4.0.0
@@ -12788,7 +13270,7 @@ snapshots:
nanoid@3.3.7: {}
- nanoid@3.3.8: {}
+ nanostores@0.11.3: {}
napi-build-utils@1.0.2:
optional: true
@@ -12817,9 +13299,7 @@ snapshots:
react: 18.2.0
react-dom: 18.2.0(react@18.2.0)
- next-tick@1.1.0: {}
-
- next@15.0.1(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
+ next@15.0.1(react-dom@18.2.0(react@18.2.0))(react@18.2.0):
dependencies:
'@next/env': 15.0.1
'@swc/counter': 0.1.3
@@ -12847,13 +13327,16 @@ snapshots:
node-abi@3.68.0:
dependencies:
- semver: 7.6.2
+ semver: 7.6.3
optional: true
node-abort-controller@3.1.1: {}
node-addon-api@5.1.0: {}
+ node-addon-api@7.1.1:
+ optional: true
+
node-domexception@1.0.0: {}
node-fetch-commonjs@3.3.2:
@@ -12863,15 +13346,34 @@ snapshots:
node-fetch-native@1.6.4: {}
- node-fetch@2.7.0:
+ node-fetch@2.7.0(encoding@0.1.13):
dependencies:
whatwg-url: 5.0.0
+ optionalDependencies:
+ encoding: 0.1.13
node-gyp-build-optional-packages@5.2.2:
dependencies:
detect-libc: 2.0.3
optional: true
+ node-gyp@8.4.1:
+ dependencies:
+ env-paths: 2.2.1
+ glob: 7.2.3
+ graceful-fs: 4.2.11
+ make-fetch-happen: 9.1.0
+ nopt: 5.0.0
+ npmlog: 6.0.2
+ rimraf: 3.0.2
+ semver: 7.6.3
+ tar: 6.2.1
+ which: 2.0.2
+ transitivePeerDependencies:
+ - bluebird
+ - supports-color
+ optional: true
+
node-mocks-http@1.15.0:
dependencies:
'@types/express': 4.17.21
@@ -12928,6 +13430,14 @@ snapshots:
gauge: 3.0.2
set-blocking: 2.0.0
+ npmlog@6.0.2:
+ dependencies:
+ are-we-there-yet: 3.0.1
+ console-control-strings: 1.1.0
+ gauge: 4.0.4
+ set-blocking: 2.0.0
+ optional: true
+
object-assign@4.1.1: {}
object-hash@3.0.0: {}
@@ -13012,6 +13522,11 @@ snapshots:
dependencies:
p-limit: 4.0.0
+ p-map@4.0.0:
+ dependencies:
+ aggregate-error: 3.1.0
+ optional: true
+
p-try@2.2.0: {}
package-json-from-dist@1.0.0: {}
@@ -13154,12 +13669,13 @@ snapshots:
camelcase-css: 2.0.1
postcss: 8.4.40
- postcss-load-config@4.0.2(postcss@8.4.40):
+ postcss-load-config@4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@18.19.42)(typescript@5.5.3)):
dependencies:
lilconfig: 3.1.2
yaml: 2.4.5
optionalDependencies:
postcss: 8.4.40
+ ts-node: 10.9.2(@types/node@18.19.42)(typescript@5.5.3)
postcss-nested@6.2.0(postcss@8.4.40):
dependencies:
@@ -13222,6 +13738,15 @@ snapshots:
process@0.11.10: {}
+ promise-inflight@1.0.1:
+ optional: true
+
+ promise-retry@2.0.1:
+ dependencies:
+ err-code: 2.0.3
+ retry: 0.12.0
+ optional: true
+
prop-types@15.8.1:
dependencies:
loose-envify: 1.4.0
@@ -13252,6 +13777,12 @@ snapshots:
punycode@2.3.1: {}
+ pvtsutils@1.3.6:
+ dependencies:
+ tslib: 2.8.1
+
+ pvutils@1.1.3: {}
+
qrcode@1.5.4:
dependencies:
dijkstrajs: 1.0.3
@@ -13319,6 +13850,11 @@ snapshots:
prop-types: 15.8.1
react: 18.2.0
+ react-day-picker@8.10.1(date-fns@3.6.0)(react@18.2.0):
+ dependencies:
+ date-fns: 3.6.0
+ react: 18.2.0
+
react-debounce-input@3.3.0(react@18.2.0):
dependencies:
lodash.debounce: 4.0.8
@@ -13608,6 +14144,9 @@ snapshots:
ret@0.2.2: {}
+ retry@0.12.0:
+ optional: true
+
reusify@1.0.4: {}
rfdc@1.4.1: {}
@@ -13640,6 +14179,8 @@ snapshots:
rotating-file-stream@3.2.3: {}
+ rou3@0.5.1: {}
+
run-parallel@1.2.0:
dependencies:
queue-microtask: 1.2.3
@@ -13692,6 +14233,8 @@ snapshots:
set-blocking@2.0.0: {}
+ set-cookie-parser@2.7.1: {}
+
set-function-length@1.2.2:
dependencies:
define-data-property: 1.1.4
@@ -13773,8 +14316,6 @@ snapshots:
is-arrayish: 0.3.2
optional: true
- sisteransi@1.0.5: {}
-
slash@3.0.0: {}
slash@5.1.0: {}
@@ -13791,6 +14332,24 @@ snapshots:
slugify@1.6.6: {}
+ smart-buffer@4.2.0:
+ optional: true
+
+ socks-proxy-agent@6.2.1:
+ dependencies:
+ agent-base: 6.0.2
+ debug: 4.3.7
+ socks: 2.8.3
+ transitivePeerDependencies:
+ - supports-color
+ optional: true
+
+ socks@2.8.3:
+ dependencies:
+ ip-address: 9.0.5
+ smart-buffer: 4.2.0
+ optional: true
+
sonic-boom@4.1.0:
dependencies:
atomic-sleep: 1.0.0
@@ -13821,6 +14380,22 @@ snapshots:
sprintf-js@1.0.3: {}
+ sprintf-js@1.1.3:
+ optional: true
+
+ sqlite3@5.1.7:
+ dependencies:
+ bindings: 1.5.0
+ node-addon-api: 7.1.1
+ prebuild-install: 7.1.2
+ tar: 6.2.1
+ optionalDependencies:
+ node-gyp: 8.4.1
+ transitivePeerDependencies:
+ - bluebird
+ - supports-color
+ optional: true
+
ssh2@1.15.0:
dependencies:
asn1: 0.2.6
@@ -13829,6 +14404,11 @@ snapshots:
cpu-features: 0.0.10
nan: 2.20.0
+ ssri@8.0.1:
+ dependencies:
+ minipass: 3.3.6
+ optional: true
+
stackback@0.0.2: {}
standard-as-callback@2.1.0: {}
@@ -14000,11 +14580,11 @@ snapshots:
tailwind-merge@2.4.0: {}
- tailwindcss-animate@1.0.7(tailwindcss@3.4.7):
+ tailwindcss-animate@1.0.7(tailwindcss@3.4.7(ts-node@10.9.2(@types/node@18.19.42)(typescript@5.5.3))):
dependencies:
- tailwindcss: 3.4.7
+ tailwindcss: 3.4.7(ts-node@10.9.2(@types/node@18.19.42)(typescript@5.5.3))
- tailwindcss@3.4.7:
+ tailwindcss@3.4.7(ts-node@10.9.2(@types/node@18.19.42)(typescript@5.5.3)):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
@@ -14023,7 +14603,7 @@ snapshots:
postcss: 8.4.40
postcss-import: 15.1.0(postcss@8.4.40)
postcss-js: 4.0.1(postcss@8.4.40)
- postcss-load-config: 4.0.2(postcss@8.4.40)
+ postcss-load-config: 4.0.2(postcss@8.4.40)(ts-node@10.9.2(@types/node@18.19.42)(typescript@5.5.3))
postcss-nested: 6.2.0(postcss@8.4.40)
postcss-selector-parser: 6.1.1
resolve: 1.22.8
@@ -14105,11 +14685,6 @@ snapshots:
through@2.3.8: {}
- timers-ext@0.1.8:
- dependencies:
- es5-ext: 0.10.64
- next-tick: 1.1.0
-
tiny-invariant@1.3.3: {}
tiny-warning@1.0.3: {}
@@ -14158,6 +14733,25 @@ snapshots:
ts-mixer@6.0.4: {}
+ ts-node@10.9.2(@types/node@18.19.42)(typescript@5.5.3):
+ dependencies:
+ '@cspotcode/source-map-support': 0.8.1
+ '@tsconfig/node10': 1.0.11
+ '@tsconfig/node12': 1.0.11
+ '@tsconfig/node14': 1.0.3
+ '@tsconfig/node16': 1.0.4
+ '@types/node': 18.19.42
+ acorn: 8.12.1
+ acorn-walk: 8.3.3
+ arg: 4.1.3
+ create-require: 1.1.1
+ diff: 4.0.2
+ make-error: 1.3.6
+ typescript: 5.5.3
+ v8-compile-cache-lib: 3.0.1
+ yn: 3.1.1
+ optional: true
+
ts-toolbelt@9.6.0: {}
tsc-alias@1.8.10:
@@ -14175,6 +14769,8 @@ snapshots:
tslib@2.6.3: {}
+ tslib@2.8.1: {}
+
tsx@4.16.2:
dependencies:
esbuild: 0.21.5
@@ -14200,14 +14796,14 @@ snapshots:
media-typer: 0.3.0
mime-types: 2.1.35
- type@2.7.3: {}
-
types-ramda@0.30.1:
dependencies:
ts-toolbelt: 9.6.0
typescript@5.5.3: {}
+ typescript@5.7.2: {}
+
ufo@1.5.4: {}
uncrypto@0.1.3: {}
@@ -14226,38 +14822,15 @@ snapshots:
unicorn-magic@0.1.0: {}
- unified@11.0.5:
+ unique-filename@1.1.1:
dependencies:
- '@types/unist': 3.0.3
- bail: 2.0.2
- devlop: 1.1.0
- extend: 3.0.2
- is-plain-obj: 4.1.0
- trough: 2.2.0
- vfile: 6.0.3
+ unique-slug: 2.0.2
+ optional: true
- unist-util-is@6.0.0:
+ unique-slug@2.0.2:
dependencies:
- '@types/unist': 3.0.3
-
- unist-util-position@5.0.0:
- dependencies:
- '@types/unist': 3.0.3
-
- unist-util-stringify-position@4.0.0:
- dependencies:
- '@types/unist': 3.0.3
-
- unist-util-visit-parents@6.0.1:
- dependencies:
- '@types/unist': 3.0.3
- unist-util-is: 6.0.0
-
- unist-util-visit@5.0.0:
- dependencies:
- '@types/unist': 3.0.3
- unist-util-is: 6.0.0
- unist-util-visit-parents: 6.0.1
+ imurmurhash: 0.1.4
+ optional: true
universal-github-app-jwt@1.1.2:
dependencies:
@@ -14318,15 +14891,12 @@ snapshots:
uuid@9.0.1: {}
- vfile-message@4.0.2:
- dependencies:
- '@types/unist': 3.0.3
- unist-util-stringify-position: 4.0.0
+ v8-compile-cache-lib@3.0.1:
+ optional: true
- vfile@6.0.3:
- dependencies:
- '@types/unist': 3.0.3
- vfile-message: 4.0.2
+ valibot@1.0.0-beta.15(typescript@5.5.3):
+ optionalDependencies:
+ typescript: 5.5.3
victory-vendor@36.9.2:
dependencies:
@@ -14489,8 +15059,6 @@ snapshots:
dependencies:
string-width: 5.1.2
- wordwrap@1.0.0: {}
-
wrap-ansi@6.2.0:
dependencies:
ansi-styles: 4.3.0
@@ -14572,6 +15140,9 @@ snapshots:
y18n: 5.0.8
yargs-parser: 21.1.1
+ yn@3.1.1:
+ optional: true
+
yocto-queue@1.1.1: {}
zenscroll@4.0.2: {}
@@ -14590,4 +15161,4 @@ snapshots:
zod@3.23.8: {}
- zwitch@2.0.4: {}
+ zod@3.24.1: {}
diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml
index 3ca0730a3..2cb911dbc 100644
--- a/pnpm-workspace.yaml
+++ b/pnpm-workspace.yaml
@@ -1,6 +1,7 @@
packages:
- "apps/dokploy"
- "apps/api"
+ - "apps/monitoring"
- "apps/schedules"
- "apps/models"
- "packages/server"