Merge pull request #772 from Dokploy/canary

v0.13.0
This commit is contained in:
Mauricio Siu
2024-11-27 23:28:40 -06:00
committed by GitHub
61 changed files with 1443 additions and 109 deletions

View File

@@ -1,6 +1,6 @@
name: Bug Report
description: Create a bug report
labels: ['bug']
labels: ["bug"]
body:
- type: markdown
attributes:
@@ -11,18 +11,27 @@ body:
- type: textarea
attributes:
label: To Reproduce
description: A step-by-step description of how to reproduce the issue, or a link to the reproducible repository.
description: |
A detailed, step-by-step description of how to reproduce the issue is required.
Please ensure your report includes clear instructions using numbered lists.
If possible, provide a link to a repository or project where the issue can be reproduced.
placeholder: |
1. Create a application
2. Click X
3. Y will happen
Make sure to:
- Use numbered lists to outline steps clearly.
- Include all relevant commands and configurations.
- Provide a link to a reproducible repository if applicable.
validations:
required: true
- type: textarea
attributes:
label: Current vs. Expected behavior
description: A clear and concise description of what the bug is, and what you expected to happen.
placeholder: 'Following the steps from the previous section, I expected A to happen, but I observed B instead'
placeholder: "Following the steps from the previous section, I expected A to happen, but I observed B instead"
validations:
required: true
- type: textarea
@@ -45,12 +54,23 @@ body:
label: Which area(s) are affected? (Select all that apply)
multiple: true
options:
- 'Installation'
- 'Application'
- 'Databases'
- 'Docker Compose'
- 'Traefik'
- 'Docker'
- "Installation"
- "Application"
- "Databases"
- "Docker Compose"
- "Traefik"
- "Docker"
- "Remote server"
- "Local Development"
validations:
required: true
- type: dropdown
attributes:
label: Are you deploying the applications where Dokploy is installed or on a remote server?
options:
- "Same server where Dokploy is installed"
- "Remote server"
- "Both"
validations:
required: true
- type: textarea
@@ -59,4 +79,16 @@ body:
description: |
Any extra information that might help us investigate.
placeholder: |
I tested on a DigitalOcean VPS with Ubuntu 20.04 and Docker version 20.10.12.
I tested on a DigitalOcean VPS with Ubuntu 20.04 and Docker version 20.10.12.
- type: dropdown
attributes:
label: Will you send a PR to fix it?
description: Let us know if you are planning to submit a pull request to address this issue.
options:
- "Yes"
- "No"
- "Maybe, need help"
validations:
required: true

View File

@@ -1,6 +1,6 @@
name: Feature Request
description: Suggest a new feature or improvement to the project
labels: ['enhancement']
labels: ["enhancement"]
body:
- type: textarea
attributes:
@@ -30,4 +30,15 @@ body:
label: Additional context
description: Add any other context or screenshots about the feature request here.
validations:
required: false
required: false
- type: dropdown
attributes:
label: Will you send a PR to implement it?
description: Let us know if you are planning to submit a pull request to implement this feature.
options:
- "Yes"
- "No"
- "Maybe, need help"
validations:
required: true

BIN
.github/sponsors/startupfame.png vendored Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

View File

@@ -87,7 +87,8 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
<div style="display: flex; gap: 30px; flex-wrap: wrap;">
<a href="https://lightspeed.run/?ref=dokploy"><img src="https://github.com/lightspeedrun.png" width="60px" alt="Lightspeed.run"/></a>
<a href="https://cloudblast.io/?ref=dokploy "><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Lightspeed.run"/></a>
<a href="https://cloudblast.io/?ref=dokploy "><img src="https://cloudblast.io/img/logo-icon.193cf13e.svg" width="250px" alt="Cloudblast.io"/></a>
<a href="https://startupfa.me/?ref=dokploy "><img src=".github/sponsors/startupfame.png" width="65px" alt="Startupfame"/></a>
</div>
### Community Backers 🤝
@@ -116,7 +117,7 @@ For detailed documentation, visit [docs.dokploy.com](https://docs.dokploy.com).
## Video Tutorial
<a href="https://youtu.be/mznYKPvhcfw">
<img src="https://dokploy.com/banner.webp" alt="Watch the video" width="400" style="border-radius:20px;"/>
<img src="https://dokploy.com/banner.png" alt="Watch the video" width="400" style="border-radius:20px;"/>
</a>
<!-- ## Supported OS

View File

@@ -37,7 +37,7 @@ const appearanceFormSchema = z.object({
theme: z.enum(["light", "dark", "system"], {
required_error: "Please select a theme.",
}),
language: z.enum(["en", "pl", "zh-Hans"], {
language: z.enum(["en", "pl", "ru", "de", "zh-Hant", "zh-Hans"], {
required_error: "Please select a language.",
}),
});
@@ -175,6 +175,9 @@ export function AppearanceForm() {
{[
{ label: "English", value: "en" },
{ label: "Polski", value: "pl" },
{ label: "Русский", value: "ru" },
{ label: "Deutsch", value: "de" },
{ label: "繁體中文", value: "zh-Hant" },
{ label: "简体中文", value: "zh-Hans" },
].map((preset) => (
<SelectItem key={preset.label} value={preset.value}>

View File

@@ -16,10 +16,11 @@ import {
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
import { generateSHA256Hash } from "@/lib/utils";
import { api } from "@/utils/api";
import { zodResolver } from "@hookform/resolvers/zod";
import { useTranslation } from "next-i18next";
import { useEffect } from "react";
import { useEffect, useMemo, useState } from "react";
import { useForm } from "react-hook-form";
import { toast } from "sonner";
import { z } from "zod";
@@ -53,6 +54,14 @@ export const ProfileForm = () => {
const { data, refetch } = api.auth.get.useQuery();
const { mutateAsync, isLoading } = api.auth.update.useMutation();
const { t } = useTranslation("settings");
const [gravatarHash, setGravatarHash] = useState<string | null>(null);
const availableAvatars = useMemo(() => {
if (gravatarHash === null) return randomImages;
return randomImages.concat([
`https://www.gravatar.com/avatar/${gravatarHash}`,
]);
}, [gravatarHash]);
const form = useForm<Profile>({
defaultValues: {
@@ -70,6 +79,12 @@ export const ProfileForm = () => {
password: "",
image: data?.image || "",
});
if (data.email) {
generateSHA256Hash(data.email).then((hash) => {
setGravatarHash(hash);
});
}
}
form.reset();
}, [form, form.reset, data]);
@@ -154,7 +169,7 @@ export const ProfileForm = () => {
value={field.value}
className="flex flex-row flex-wrap gap-2 max-xl:justify-center"
>
{randomImages.map((image) => (
{availableAvatars.map((image) => (
<FormItem key={image}>
<FormLabel className="[&:has([data-state=checked])>img]:border-primary [&:has([data-state=checked])>img]:border-1 [&:has([data-state=checked])>img]:p-px cursor-pointer">
<FormControl>

View File

@@ -48,8 +48,8 @@ export const UpdateServer = () => {
<li>Some bug that is blocking to use some features</li>
</ul>
<AlertBlock type="info">
Please we recommend to see the latest version to see if there are
any breaking changes before updating. Go to{" "}
We recommend checking the latest version for any breaking changes
before updating. Go to{" "}
<Link
href="https://github.com/Dokploy/dokploy/releases"
target="_blank"

View File

@@ -4,3 +4,11 @@ import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export async function generateSHA256Hash(text: string) {
const encoder = new TextEncoder();
const data = encoder.encode(text);
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
const hashArray = Array.from(new Uint8Array(hashBuffer));
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
}

View File

@@ -2,7 +2,7 @@
module.exports = {
i18n: {
defaultLocale: "en",
locales: ["en", "pl", "zh-Hans"],
locales: ["en", "pl", "ru", "de", "zh-Hant", "zh-Hans"],
localeDetection: false,
},
fallbackLng: "en",

View File

@@ -1,6 +1,6 @@
{
"name": "dokploy",
"version": "v0.12.0",
"version": "v0.13.0",
"private": true,
"license": "Apache-2.0",
"type": "module",

View File

@@ -71,7 +71,7 @@ export default api.withTRPC(
{
i18n: {
defaultLocale: "en",
locales: ["en", "pl", "zh-Hans"],
locales: ["en", "pl", "ru", "de", "zh-Hant", "zh-Hans"],
localeDetection: false,
},
fallbackLng: "en",

View File

@@ -8,7 +8,6 @@ import { createServerSideHelpers } from "@trpc/react-query/server";
import type { GetServerSidePropsContext } from "next";
import React, { type ReactElement } from "react";
import superjson from "superjson";
import nextI18NextConfig from "../../../next-i18next.config.cjs";
const Page = () => {
return (

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,44 @@
{
"settings.common.save": "Speichern",
"settings.server.domain.title": "Server-Domain",
"settings.server.domain.description": "Füg eine Domain zu deiner Server-Anwendung hinzu.",
"settings.server.domain.form.domain": "Domain",
"settings.server.domain.form.letsEncryptEmail": "Let's Encrypt E-Mail",
"settings.server.domain.form.certificate.label": "Zertifikat",
"settings.server.domain.form.certificate.placeholder": "Wähl ein Zertifikat aus",
"settings.server.domain.form.certificateOptions.none": "Keins",
"settings.server.domain.form.certificateOptions.letsencrypt": "Let's Encrypt (Standard)",
"settings.server.webServer.title": "Web-Server",
"settings.server.webServer.description": "Lade den Web-Server neu oder reinige ihn.",
"settings.server.webServer.actions": "Aktionen",
"settings.server.webServer.reload": "Neu laden",
"settings.server.webServer.watchLogs": "Logs anschauen",
"settings.server.webServer.updateServerIp": "Server-IP Aktualisieren",
"settings.server.webServer.server.label": "Server",
"settings.server.webServer.traefik.label": "Traefik",
"settings.server.webServer.traefik.modifyEnv": "Umgebungsvariablen ändern",
"settings.server.webServer.storage.label": "Speicherplatz",
"settings.server.webServer.storage.cleanUnusedImages": "Nicht genutzte Bilder löschen",
"settings.server.webServer.storage.cleanUnusedVolumes": "Nicht genutzte Volumes löschen",
"settings.server.webServer.storage.cleanStoppedContainers": "Gestoppte Container löschen",
"settings.server.webServer.storage.cleanDockerBuilder": "Docker Builder & System bereinigen",
"settings.server.webServer.storage.cleanMonitoring": "Monitoring bereinigen",
"settings.server.webServer.storage.cleanAll": "Alles bereinigen",
"settings.profile.title": "Konto",
"settings.profile.description": "Ändere die Details deines Profiles hier.",
"settings.profile.email": "E-Mail",
"settings.profile.password": "Passwort",
"settings.profile.avatar": "Avatar",
"settings.appearance.title": "Aussehen",
"settings.appearance.description": "Pass das Design deines Dashboards an.",
"settings.appearance.theme": "Theme",
"settings.appearance.themeDescription": "Wähl ein Theme für dein Dashboard aus",
"settings.appearance.themes.light": "Hell",
"settings.appearance.themes.dark": "Dunkel",
"settings.appearance.themes.system": "System",
"settings.appearance.language": "Sprache",
"settings.appearance.languageDescription": "Wähl eine Sprache für dein Dashboard aus"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,44 @@
{
"settings.common.save": "Сохранить",
"settings.server.domain.title": "Домен сервера",
"settings.server.domain.description": "Установите домен для вашего серверного приложения Dokploy.",
"settings.server.domain.form.domain": "Домен",
"settings.server.domain.form.letsEncryptEmail": "Email для 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.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.traefik.modifyEnv": "Изменить переменные окружения",
"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": "Email",
"settings.profile.password": "Пароль",
"settings.profile.avatar": "Аватар",
"settings.appearance.title": "Внешний вид",
"settings.appearance.description": "Настройте тему Dokploy.",
"settings.appearance.theme": "Тема",
"settings.appearance.themeDescription": "Выберите тему системной панели",
"settings.appearance.themes.light": "Светлая",
"settings.appearance.themes.dark": "Темная",
"settings.appearance.themes.system": "Системная",
"settings.appearance.language": "Язык",
"settings.appearance.languageDescription": "Select a language for your dashboard"
}

View File

@@ -0,0 +1 @@
{}

View File

@@ -0,0 +1,40 @@
{
"settings.common.save": "儲存",
"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": "Web 伺服器",
"settings.server.webServer.description": "管理 Web 伺服器。",
"settings.server.webServer.actions": "操作",
"settings.server.webServer.reload": "重新載入",
"settings.server.webServer.watchLogs": "查看日誌",
"settings.server.webServer.server.label": "伺服器",
"settings.server.webServer.traefik.label": "Traefik",
"settings.server.webServer.traefik.modifyEnv": "修改環境變數",
"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": "選擇儀表板語言"
}

View File

@@ -0,0 +1,3 @@
<svg xmlns="http://www.w3.org/2000/svg" width="undefined" height="undefined" viewBox="0 0 24 24">
<path fill="#0ea5e9" d="M0 12c0 6.629 5.371 12 12 12s12-5.371 12-12S18.629 0 12 0S0 5.371 0 12m17.008 5.29H11.44a5.57 5.57 0 0 1-5.562-5.567A5.57 5.57 0 0 1 11.44 6.16a5.57 5.57 0 0 1 5.567 5.563Z"/>
</svg>

After

Width:  |  Height:  |  Size: 306 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 -1 104 106">
<path fill="#231f20" d="M51.87 0C23.71 0 0 22.83 0 51v52.81l51.86-.05c28.16 0 51-23.71 51-51.87S80 0 51.87 0Z"/>
<path fill="#fff9ae" d="M52.37 19.74a31.62 31.62 0 0 0-27.79 46.67l-5.72 18.4 20.54-4.64a31.61 31.61 0 1 0 13-60.43Z"/>
<path fill="#00aeef" d="M77.45 32.12a31.6 31.6 0 0 1-38.05 48l-20.54 4.7 20.91-2.47a31.6 31.6 0 0 0 37.68-50.23Z"/>
<path fill="#00a94f" d="M71.63 26.29A31.6 31.6 0 0 1 38.8 78l-19.94 6.82 20.54-4.65a31.6 31.6 0 0 0 32.23-53.88Z"/>
<path fill="#f15d22" d="M26.47 67.11a31.61 31.61 0 0 1 51-35 31.61 31.61 0 0 0-52.89 34.3l-5.72 18.4Z"/>
<path fill="#e31b23" d="M24.58 66.41a31.61 31.61 0 0 1 47.05-40.12 31.61 31.61 0 0 0-49 39.63l-3.76 18.9Z"/>
</svg>

After

Width:  |  Height:  |  Size: 761 B

View File

@@ -0,0 +1,5 @@
<svg width="130" height="130" viewBox="0 0 130 130" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M63.1957 12.5861C65.1413 14.0499 66.2222 16.0447 66.4383 18.5707L66.4722 18.5513L66.4721 19.1439C66.4765 19.2753 66.4787 19.408 66.4787 19.5421L66.4721 19.5457L66.4721 70.8151C66.5176 73.4895 67.517 75.6073 69.4703 77.1684C72.3982 79.2596 75.4512 79.454 78.6294 77.7515L37.0683 101.69C33.9831 103.279 31.0322 103.141 28.2155 101.276C26.274 99.8155 25.1936 97.826 24.9743 95.3078L24.9325 95.3318L24.9326 42.812C24.8338 40.2483 23.8366 38.2089 21.9409 36.6938C21.0056 36.0258 20.0575 35.5513 19.0967 35.2704C17.732 34.9092 17.732 33.2068 18.738 32.6801C21.0305 31.3597 32.8988 24.5238 54.3429 12.1726C57.4281 10.5835 60.3791 10.7214 63.1957 12.5861Z" fill="#9DE8FF"/>
<path d="M66.472 70.8079C66.4429 65.6445 62.2284 58.1373 53.6907 58.1373C51.3183 58.1373 49.1069 58.7573 47.0566 59.9971L85.1604 38.0562C87.0493 36.9588 89.2462 36.3301 91.5906 36.3301C98.621 36.3301 104.326 41.9843 104.372 48.9767C104.372 48.9781 104.372 49.0065 104.372 49.0621L104.372 87.79C104.418 90.4635 105.417 92.5806 107.37 94.1413C108.316 94.8171 109.275 95.2948 110.248 95.5744C111.649 96.0291 111.488 97.668 110.58 98.1513C105.284 101.202 94.6267 107.35 78.608 116.598C75.5228 118.187 72.5719 118.049 69.7552 116.184C67.8089 114.72 66.728 112.724 66.5124 110.197L66.4722 110.22V100.642C66.4721 90.69 66.4721 70.8199 66.472 70.8079Z" fill="#106BF3"/>
<path d="M53.6907 58.1373C60.7497 58.1373 66.4722 63.8376 66.4722 70.8692C66.4722 77.9274 66.4722 82.5578 66.4722 84.7602C66.4722 84.7602 44.4801 97.4179 41.2926 99.2526C41.0435 99.3959 36.9265 101.759 36.9265 101.759C40.583 99.3959 40.9093 96.2258 40.9093 94.8181C40.9093 90.7365 40.9093 82.7535 40.9093 70.8692C40.9093 63.8376 46.6317 58.1373 53.6907 58.1373Z" fill="#57AAF9"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

View File

@@ -0,0 +1,9 @@
<svg width="96" height="96" viewBox="0 0 96 96" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect x="0.417391" y="0.417391" width="95.1652" height="95.1652" rx="47.5826" fill="white"/>
<rect x="0.417391" y="0.417391" width="95.1652" height="95.1652" rx="47.5826" stroke="#E9EAEC" stroke-width="0.834783"/>
<path d="M45.416 33.5745C50.1219 37.7635 53.9144 42.2526 56.3551 46.4832C60.5471 38.9452 63.3482 29.9879 63.3835 24.2829C63.3835 24.2425 63.3835 24.2057 63.3835 24.1715C63.3835 15.7297 55.0081 12.4443 47.7933 12.4443C40.5786 12.4443 32.2031 15.7297 32.2031 24.1715C32.2031 24.2866 32.2031 24.4409 32.2031 24.627C36.2246 26.4246 40.9914 29.6364 45.416 33.5745Z" fill="#FA2921"/>
<path d="M19.7443 56.5951C22.6855 53.3048 27.1978 49.739 32.2898 46.7243C37.707 43.5185 43.1254 41.2789 47.8812 40.254C42.0463 33.9159 34.4394 28.4693 29.0527 26.673C29.0149 26.6607 28.9796 26.6497 28.9479 26.6387C20.9622 24.0305 15.2661 31.0236 13.0374 37.9225C10.8087 44.8214 11.3275 53.846 19.3132 56.4542C19.4216 56.4897 19.5677 56.5375 19.7443 56.5951Z" fill="#ED79B5"/>
<path d="M82.6341 37.8063C80.4054 30.9074 74.7093 23.9143 66.7236 26.5225C66.614 26.558 66.4679 26.6057 66.2925 26.6633C65.8358 31.0629 64.2708 36.6149 61.9129 42.0627C59.4053 47.8571 56.3301 52.8715 53.082 56.5119C61.5074 58.1919 70.8462 58.1013 76.2536 56.3723C76.2914 56.3601 76.3267 56.3478 76.3583 56.338C84.344 53.7286 84.8629 44.704 82.6341 37.8063Z" fill="#FFB400"/>
<path d="M40.6729 63.3397C39.315 57.1694 38.8704 51.2954 39.3698 46.4316C31.5716 50.0525 24.0694 55.6436 20.7044 60.2392C20.6812 60.271 20.6593 60.3017 20.6398 60.3286C15.7049 67.1589 20.5595 74.7668 26.3968 79.0293C32.2329 83.293 40.9299 85.5853 45.866 78.755C45.9342 78.6619 46.0243 78.537 46.1327 78.3864C43.9295 74.5574 41.9493 69.1402 40.6729 63.3397Z" fill="#1E83F7"/>
<path d="M74.9007 59.8885C70.5979 60.8118 64.8628 61.031 58.9804 60.4591C52.7241 59.8518 47.0317 58.4607 42.584 56.4795C43.5985 65.0547 46.5701 73.9569 49.8767 78.5941C49.8998 78.626 49.9218 78.6566 49.9413 78.6835C54.8761 85.5138 63.5731 83.2215 69.4104 78.9578C75.2466 74.6941 80.1023 67.0862 75.1674 60.2571C75.0992 60.164 75.0091 60.0391 74.9007 59.8885Z" fill="#18C249"/>
</svg>

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 47 KiB

View File

@@ -0,0 +1 @@
<svg data-name="Ebene 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 266 266"><defs><linearGradient id="a" x1="45.04" y1="231.72" x2="231.72" y2="45.04" gradientUnits="userSpaceOnUse" gradientTransform="translate(-5.38 -5.38)"><stop offset="0" stop-color="#fff"/><stop offset="0" stop-color="#b8edff"/><stop offset="1" stop-color="#d4b8ff"/></linearGradient></defs><circle cx="133" cy="133" r="132" style="fill:url(#a)"/><path data-name="Logo Pfad" d="m224.19 176.51-4 24.19M41.91 177.5l14.81 14m95.76-137.65L56.62 191.31a.09.09 0 0 0 .07.15l163.41 9.37a.09.09 0 0 0 .09-.13L152.62 53.87a.1.1 0 0 0-.14-.02zm-19.74-13.29L41.8 177.31a.13.13 0 0 0 .11.19l182.18-.8a.12.12 0 0 0 .1-.19L132.95 40.56a.12.12 0 0 0-.21 0zm.11-.16 19.77 13.32" style="fill:none;stroke:#1d1d1b;stroke-miterlimit:10;stroke-width:6px"/></svg>

After

Width:  |  Height:  |  Size: 819 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" viewBox="0 0 40 40">
<g id="ss11151339769_1">
<path d="M 0 40 L 0 0 L 40 0 L 40 40 Z" fill="transparent"></path>
<path d="M 34.95 0 L 5.05 0 C 2.262 0 0 2.262 0 5.05 L 0 34.95 C 0 37.738 2.262 40 5.05 40 L 34.95 40 C 37.738 40 40 37.738 40 34.95 L 40 5.05 C 40 2.262 37.738 0 34.95 0 Z M 8.021 14.894 C 8.021 12.709 9.794 10.935 11.979 10.935 L 19.6 10.935 C 19.712 10.935 19.815 11.003 19.862 11.106 C 19.909 11.209 19.888 11.329 19.812 11.415 L 18.141 13.229 C 17.85 13.544 17.441 13.726 17.012 13.726 L 12 13.726 C 11.344 13.726 10.812 14.259 10.812 14.915 L 10.812 17.909 C 10.812 18.294 10.5 18.606 10.115 18.606 L 8.721 18.606 C 8.335 18.606 8.024 18.294 8.024 17.909 L 8.024 14.894 Z M 31.729 25.106 C 31.729 27.291 29.956 29.065 27.771 29.065 L 24.532 29.065 C 22.347 29.065 20.574 27.291 20.574 25.106 L 20.574 19.438 C 20.574 19.053 20.718 18.682 20.979 18.397 L 22.868 16.347 C 22.947 16.262 23.071 16.232 23.182 16.274 C 23.291 16.318 23.365 16.421 23.365 16.538 L 23.365 25.088 C 23.365 25.744 23.897 26.276 24.553 26.276 L 27.753 26.276 C 28.409 26.276 28.941 25.744 28.941 25.088 L 28.941 14.915 C 28.941 14.259 28.409 13.726 27.753 13.726 L 24.032 13.726 C 23.606 13.726 23.2 13.906 22.909 14.218 L 11.812 26.276 L 18.479 26.276 C 18.865 26.276 19.176 26.588 19.176 26.974 L 19.176 28.368 C 19.176 28.753 18.865 29.065 18.479 29.065 L 9.494 29.065 C 8.679 29.065 8.018 28.403 8.018 27.588 L 8.018 26.85 C 8.018 26.479 8.156 26.124 8.409 25.85 L 20.85 12.335 C 21.674 11.441 22.829 10.935 24.044 10.935 L 27.768 10.935 C 29.953 10.935 31.726 12.709 31.726 14.894 L 31.726 25.106 Z" fill="rgb(0,0,0)"></path>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -79,6 +79,8 @@ export const composeRouter = createTRPCRouter({
if (ctx.user.rol === "user") {
await addNewService(ctx.user.authId, newService.composeId);
}
return newService;
} catch (error) {
throw error;
}

View File

@@ -0,0 +1,76 @@
version: '3'
x-base-config: &base-config
image: chatwoot/chatwoot:v3.14.1
volumes:
- chatwoot-storage:/app/storage
networks:
- dokploy-network
environment:
- FRONTEND_URL=${FRONTEND_URL}
- SECRET_KEY_BASE=${SECRET_KEY_BASE}
- RAILS_ENV=${RAILS_ENV}
- NODE_ENV=${NODE_ENV}
- INSTALLATION_ENV=${INSTALLATION_ENV}
- RAILS_LOG_TO_STDOUT=${RAILS_LOG_TO_STDOUT}
- LOG_LEVEL=${LOG_LEVEL}
- DEFAULT_LOCALE=${DEFAULT_LOCALE}
- POSTGRES_HOST=${POSTGRES_HOST}
- POSTGRES_PORT=${POSTGRES_PORT}
- POSTGRES_DATABASE=${POSTGRES_DATABASE}
- POSTGRES_USERNAME=${POSTGRES_USERNAME}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- REDIS_URL=${REDIS_URL}
- ENABLE_ACCOUNT_SIGNUP=${ENABLE_ACCOUNT_SIGNUP}
- ACTIVE_STORAGE_SERVICE=${ACTIVE_STORAGE_SERVICE}
services:
chatwoot-rails:
<<: *base-config
depends_on:
chatwoot-postgres:
condition: service_started
chatwoot-redis:
condition: service_started
entrypoint: docker/entrypoints/rails.sh
command: ['bundle', 'exec', 'sh', '-c', 'rails db:chatwoot_prepare && rails s -p 3000 -b 0.0.0.0']
restart: always
chatwoot-sidekiq:
<<: *base-config
depends_on:
chatwoot-postgres:
condition: service_started
chatwoot-redis:
condition: service_started
command: ['bundle', 'exec', 'sidekiq', '-C', 'config/sidekiq.yml']
restart: always
chatwoot-postgres:
image: postgres:12
restart: always
volumes:
- chatwoot-postgres-data:/var/lib/postgresql/data
networks:
- dokploy-network
environment:
- POSTGRES_DB=${POSTGRES_DATABASE}
- POSTGRES_USER=${POSTGRES_USERNAME}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
chatwoot-redis:
image: redis:alpine
restart: always
volumes:
- chatwoot-redis-data:/data
networks:
- dokploy-network
networks:
dokploy-network:
external: true
volumes:
chatwoot-storage:
chatwoot-postgres-data:
chatwoot-redis-data:

View File

@@ -0,0 +1,46 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const secretKeyBase = generateBase64(64);
const postgresPassword = generatePassword();
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "chatwoot-rails",
},
];
const envs = [
`FRONTEND_URL=http://${mainDomain}`,
`SECRET_KEY_BASE=${secretKeyBase}`,
"RAILS_ENV=production",
"NODE_ENV=production",
"INSTALLATION_ENV=docker",
"RAILS_LOG_TO_STDOUT=true",
"LOG_LEVEL=info",
"DEFAULT_LOCALE=en",
"POSTGRES_HOST=chatwoot-postgres",
"POSTGRES_PORT=5432",
"POSTGRES_DATABASE=chatwoot",
"POSTGRES_USERNAME=postgres",
`POSTGRES_PASSWORD=${postgresPassword}`,
"REDIS_URL=redis://chatwoot-redis:6379",
"ENABLE_ACCOUNT_SIGNUP=false",
"ACTIVE_STORAGE_SERVICE=local",
];
return {
domains,
envs,
};
}

View File

@@ -1,14 +1,13 @@
version: "3.8"
services:
mysql:
tickets-postgres:
image: mysql:8
restart: unless-stopped
hostname: mysql
networks:
- dokploy-network
volumes:
- tickets-mysql:/var/lib/mysql
- tickets-mysql-data:/var/lib/mysql
environment:
MYSQL_DATABASE: ${MYSQL_DATABASE}
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
@@ -20,23 +19,22 @@ services:
timeout: 5s
retries: 5
bot:
tickets-app:
image: eartharoid/discord-tickets:4.0.21
depends_on:
mysql:
tickets-postgres:
condition: service_healthy
restart: unless-stopped
hostname: bot
networks:
- dokploy-network
volumes:
- tickets-bot:/home/container/user
- tickets-app-data:/home/container/user
- /etc/timezone:/etc/timezone:ro
- /etc/localtime:/etc/localtime:ro
tty: true
stdin_open: true
environment:
DB_CONNECTION_URL: mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@mysql/${MYSQL_DATABASE}
DB_CONNECTION_URL: mysql://${MYSQL_USER}:${MYSQL_PASSWORD}@tickets-postgres/${MYSQL_DATABASE}
DISCORD_SECRET: ${DISCORD_SECRET}
DISCORD_TOKEN: ${DISCORD_TOKEN}
ENCRYPTION_KEY: ${ENCRYPTION_KEY}
@@ -49,6 +47,10 @@ services:
PUBLISH_COMMANDS: "true"
SUPER: ${SUPER_USERS}
networks:
dokploy-network:
external: true
volumes:
tickets-mysql:
tickets-bot:
tickets-mysql-data:
tickets-app-data:

View File

@@ -13,7 +13,6 @@ export function generate(schema: Schema): Template {
const mysqlUser = "tickets";
const mysqlDatabase = "tickets";
// Generate encryption key in the format they use
const encryptionKey = Array.from({ length: 48 }, () =>
Math.floor(Math.random() * 16).toString(16),
).join("");
@@ -22,7 +21,7 @@ export function generate(schema: Schema): Template {
{
host: mainDomain,
port: 8169,
serviceName: "bot",
serviceName: "tickets-app",
},
];
@@ -33,7 +32,6 @@ export function generate(schema: Schema): Template {
`MYSQL_ROOT_PASSWORD=${mysqlRootPassword}`,
`MYSQL_USER=${mysqlUser}`,
`ENCRYPTION_KEY=${encryptionKey}`,
// These need to be set by the user through the UI
"# Follow the guide at: https://discordtickets.app/self-hosting/installation/docker/#creating-the-discord-application",
"DISCORD_SECRET=",
"DISCORD_TOKEN=",

View File

@@ -0,0 +1,94 @@
version: '3.7'
services:
discourse-db:
image: docker.io/bitnami/postgresql:17
networks:
- dokploy-network
volumes:
- discourse-postgresql-data:/bitnami/postgresql
environment:
POSTGRESQL_USERNAME: bn_discourse
POSTGRESQL_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRESQL_DATABASE: bitnami_discourse
healthcheck:
test: ["CMD-SHELL", "pg_isready -U bn_discourse -d bitnami_discourse"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
discourse-redis:
image: docker.io/bitnami/redis:7.4
networks:
- dokploy-network
volumes:
- discourse-redis-data:/bitnami/redis
environment:
REDIS_PASSWORD: ${REDIS_PASSWORD}
healthcheck:
test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
discourse-app:
image: docker.io/bitnami/discourse:3.3.2
networks:
- dokploy-network
volumes:
- discourse-data:/bitnami/discourse
depends_on:
discourse-db:
condition: service_healthy
discourse-redis:
condition: service_healthy
environment:
DISCOURSE_HOST: ${DISCOURSE_HOST}
DISCOURSE_DATABASE_HOST: discourse-db
DISCOURSE_DATABASE_PORT_NUMBER: 5432
DISCOURSE_DATABASE_USER: bn_discourse
DISCOURSE_DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
DISCOURSE_DATABASE_NAME: bitnami_discourse
DISCOURSE_REDIS_HOST: discourse-redis
DISCOURSE_REDIS_PORT_NUMBER: 6379
DISCOURSE_REDIS_PASSWORD: ${REDIS_PASSWORD}
# Optional: Configure SMTP for email delivery
# DISCOURSE_SMTP_HOST: ${SMTP_HOST}
# DISCOURSE_SMTP_PORT: ${SMTP_PORT}
# DISCOURSE_SMTP_USER: ${SMTP_USER}
# DISCOURSE_SMTP_PASSWORD: ${SMTP_PASSWORD}
restart: unless-stopped
discourse-sidekiq:
image: docker.io/bitnami/discourse:3.3.2
networks:
- dokploy-network
volumes:
- discourse-sidekiq-data:/bitnami/discourse
depends_on:
- discourse-app
command: /opt/bitnami/scripts/discourse-sidekiq/run.sh
environment:
DISCOURSE_HOST: ${DISCOURSE_HOST}
DISCOURSE_DATABASE_HOST: discourse-db
DISCOURSE_DATABASE_PORT_NUMBER: 5432
DISCOURSE_DATABASE_USER: bn_discourse
DISCOURSE_DATABASE_PASSWORD: ${POSTGRES_PASSWORD}
DISCOURSE_DATABASE_NAME: bitnami_discourse
DISCOURSE_REDIS_HOST: discourse-redis
DISCOURSE_REDIS_PORT_NUMBER: 6379
DISCOURSE_REDIS_PASSWORD: ${REDIS_PASSWORD}
# Optional: Configure SMTP for email delivery
# DISCOURSE_SMTP_HOST: ${SMTP_HOST}
# DISCOURSE_SMTP_PORT: ${SMTP_PORT}
# DISCOURSE_SMTP_USER: ${SMTP_USER}
# DISCOURSE_SMTP_PASSWORD: ${SMTP_PASSWORD}
restart: unless-stopped
volumes:
discourse-postgresql-data:
discourse-redis-data:
discourse-data:
discourse-sidekiq-data:

View File

@@ -0,0 +1,37 @@
import {
type DomainSchema,
type Schema,
type Template,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const postgresPassword = generatePassword();
const redisPassword = generatePassword();
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "discourse-app",
},
];
const envs = [
`DISCOURSE_HOST=${mainDomain}`,
`POSTGRES_PASSWORD=${postgresPassword}`,
`REDIS_PASSWORD=${redisPassword}`,
"# Optional: Configure SMTP for email delivery",
"# SMTP_HOST=smtp.example.com",
"# SMTP_PORT=587",
"# SMTP_USER=your_smtp_user",
"# SMTP_PASSWORD=your_smtp_password",
];
return {
domains,
envs,
};
}

View File

@@ -0,0 +1,48 @@
services:
heyform:
image: heyform/community-edition:latest
restart: always
volumes:
# Persist uploaded images
- heyform-data:/app/static/upload
depends_on:
- mongo
- redis
ports:
- 8000
env_file:
- .env
environment:
MONGO_URI: 'mongodb://mongo:27017/heyform'
REDIS_HOST: redis
REDIS_PORT: 6379
networks:
- heyform-network
mongo:
image: percona/percona-server-mongodb:4.4
restart: always
networks:
- heyform-network
volumes:
# Persist MongoDB data
- mongo-data:/data/db
redis:
image: redis
restart: always
command: "redis-server --appendonly yes"
networks:
- heyform-network
volumes:
# Persist KeyDB data
- redis-data:/data
networks:
heyform-network:
driver: bridge
volumes:
heyform-data:
mongo-data:
redis-data:

View File

@@ -0,0 +1,32 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const sessionKey = generateBase64(64);
const formEncryptionKey = generateBase64(64);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 8000,
serviceName: "heyform",
},
];
const envs = [
`APP_HOMEPAGE_URL=http://${mainDomain}`,
`SESSION_KEY=${sessionKey}`,
`FORM_ENCRYPTION_KEY=${formEncryptionKey}`,
];
return {
envs,
domains,
};
}

View File

@@ -0,0 +1,111 @@
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
depends_on:
immich-redis:
condition: service_healthy
immich-database:
condition: service_healthy
environment:
PORT: 2283
SERVER_URL: ${SERVER_URL}
FRONT_BASE_URL: ${FRONT_BASE_URL}
# Database Configuration
DB_HOSTNAME: ${DB_HOSTNAME}
DB_PORT: ${DB_PORT}
DB_USERNAME: ${DB_USERNAME}
DB_PASSWORD: ${DB_PASSWORD}
DB_DATABASE_NAME: ${DB_DATABASE_NAME}
# Redis Configuration
REDIS_HOSTNAME: ${REDIS_HOSTNAME}
REDIS_PORT: ${REDIS_PORT}
REDIS_DBINDEX: ${REDIS_DBINDEX}
# Server Configuration
TZ: ${TZ}
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:2283/server-info/ping"]
interval: 30s
timeout: 10s
retries: 3
immich-machine-learning:
image: ghcr.io/immich-app/immich-machine-learning:v1.121.0
networks:
- dokploy-network
volumes:
- immich-model-cache:/cache
environment:
REDIS_HOSTNAME: ${REDIS_HOSTNAME}
REDIS_PORT: ${REDIS_PORT}
REDIS_DBINDEX: ${REDIS_DBINDEX}
restart: always
healthcheck:
test: ["CMD", "curl", "-f", "http://localhost:3003/ping"]
interval: 30s
timeout: 10s
retries: 3
immich-redis:
image: redis:6.2-alpine
networks:
- dokploy-network
volumes:
- immich-redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
restart: always
immich-database:
image: tensorchord/pgvecto-rs:pg14-v0.2.0
networks:
- dokploy-network
volumes:
- immich-postgres:/var/lib/postgresql/data
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_USER: ${DB_USERNAME}
POSTGRES_DB: immich
POSTGRES_INITDB_ARGS: '--data-checksums'
healthcheck:
test: pg_isready -U ${DB_USERNAME} -d immich || exit 1
interval: 10s
timeout: 5s
retries: 5
command:
[
'postgres',
'-c',
'shared_preload_libraries=vectors.so',
'-c',
'search_path="$$user", public, vectors',
'-c',
'logging_collector=on',
'-c',
'max_wal_size=2GB',
'-c',
'shared_buffers=512MB',
'-c',
'wal_compression=on',
]
restart: always
networks:
dokploy-network:
external: true
volumes:
immich-model-cache:
immich-postgres:
immich-library:
immich-redis-data:

View File

@@ -0,0 +1,46 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const dbPassword = generatePassword();
const dbUser = "immich";
const appSecret = generateBase64(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 2283,
serviceName: "immich-server",
},
];
const envs = [
`IMMICH_HOST=${mainDomain}`,
`SERVER_URL=https://${mainDomain}`,
`FRONT_BASE_URL=https://${mainDomain}`,
"# Database Configuration",
"DB_HOSTNAME=immich-database",
"DB_PORT=5432",
`DB_USERNAME=${dbUser}`,
`DB_PASSWORD=${dbPassword}`,
"DB_DATABASE_NAME=immich",
"# Redis Configuration",
"REDIS_HOSTNAME=immich-redis",
"REDIS_PORT=6379",
"REDIS_DBINDEX=0",
"# Server Configuration",
"TZ=UTC",
];
return {
domains,
envs,
};
}

View File

@@ -1,12 +1,12 @@
version: "3.8"
services:
invoiceshelf_db:
invoiceshelf-postgres:
image: postgres:15
networks:
- dokploy-network
volumes:
- postgres_data:/var/lib/postgresql/data
- invoiceshelf-postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=${DB_PASSWORD}
- POSTGRES_USER=${DB_USERNAME}
@@ -17,13 +17,13 @@ services:
timeout: 5s
retries: 5
invoiceshelf:
invoiceshelf-app:
image: invoiceshelf/invoiceshelf:latest
networks:
- dokploy-network
volumes:
- app_data:/data
- app_conf:/conf
- invoiceshelf-app-data:/data
- invoiceshelf-app-conf:/conf
environment:
- PHP_TZ=UTC
- TIMEZONE=UTC
@@ -32,7 +32,7 @@ services:
- APP_DEBUG=false
- APP_URL=http://${INVOICESHELF_HOST}
- DB_CONNECTION=pgsql
- DB_HOST=invoiceshelf_db
- DB_HOST=invoiceshelf-postgres
- DB_PORT=5432
- DB_DATABASE=${DB_DATABASE}
- DB_USERNAME=${DB_USERNAME}
@@ -46,10 +46,14 @@ services:
- SANCTUM_STATEFUL_DOMAINS=${INVOICESHELF_HOST}
- STARTUP_DELAY=10
depends_on:
invoiceshelf_db:
invoiceshelf-postgres:
condition: service_healthy
networks:
dokploy-network:
external: true
volumes:
postgres_data:
app_data:
app_conf:
invoiceshelf-postgres-data:
invoiceshelf-app-data:
invoiceshelf-app-conf:

View File

@@ -16,7 +16,7 @@ export function generate(schema: Schema): Template {
{
host: mainDomain,
port: 80,
serviceName: "invoiceshelf",
serviceName: "invoiceshelf-app",
},
];

View File

@@ -0,0 +1,14 @@
services:
ontime:
image: getontime/ontime:v3.8.0
ports:
- 4001
- 8888
- 9999
volumes:
- ontime-data:/data/
environment:
- TZ
restart: unless-stopped
volumes:
ontime-data:

View File

@@ -0,0 +1,25 @@
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: 4001,
serviceName: "ontime",
},
];
const envs = ["TZ=UTC"];
return {
domains,
envs,
};
}

View File

@@ -1,13 +1,13 @@
version: "3.8"
services:
peppermint_postgres:
peppermint-postgres:
image: postgres:latest
restart: always
networks:
- dokploy-network
volumes:
- pgdata:/var/lib/postgresql/data
- peppermint-postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: peppermint
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
@@ -18,19 +18,23 @@ services:
timeout: 5s
retries: 5
peppermint:
peppermint-app:
image: pepperlabs/peppermint:latest
restart: always
networks:
- dokploy-network
depends_on:
peppermint_postgres:
peppermint-postgres:
condition: service_healthy
environment:
DB_USERNAME: "peppermint"
DB_PASSWORD: ${POSTGRES_PASSWORD}
DB_HOST: "peppermint_postgres"
DB_HOST: "peppermint-postgres"
SECRET: ${SECRET}
networks:
dokploy-network:
external: true
volumes:
pgdata:
peppermint-postgres-data:

View File

@@ -8,27 +8,24 @@ import {
} from "../utils";
export function generate(schema: Schema): Template {
// Generate domains and secrets
const mainDomain = generateRandomDomain(schema);
const apiDomain = generateRandomDomain(schema);
const postgresPassword = generatePassword();
const secret = generateBase64(32);
// Configure domain routing
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "peppermint",
serviceName: "peppermint-app",
},
{
host: apiDomain,
port: 5003,
serviceName: "peppermint",
serviceName: "peppermint-app",
},
];
// Set environment variables
const envs = [
`MAIN_DOMAIN=${mainDomain}`,
`API_DOMAIN=${apiDomain}`,

View File

@@ -0,0 +1,78 @@
services:
photoprism:
image: photoprism/photoprism:latest
stop_grace_period: 10s
depends_on:
- mariadb
security_opt:
- seccomp:unconfined
- apparmor:unconfined
networks:
- dokploy-network
environment:
PHOTOPRISM_ADMIN_USER: "admin"
PHOTOPRISM_ADMIN_PASSWORD: ${ADMIN_PASSWORD}
PHOTOPRISM_AUTH_MODE: "password"
PHOTOPRISM_SITE_URL: "http://localhost:2342/"
PHOTOPRISM_DISABLE_TLS: "false"
PHOTOPRISM_DEFAULT_TLS: "false"
PHOTOPRISM_ORIGINALS_LIMIT: 5000 # file size limit for originals in MB (increase for high-res video)
PHOTOPRISM_HTTP_COMPRESSION: "gzip"
PHOTOPRISM_LOG_LEVEL: "info" # log level: trace, debug, info, warning, error, fatal, or panic
PHOTOPRISM_READONLY: "false"
PHOTOPRISM_EXPERIMENTAL: "false"
PHOTOPRISM_DISABLE_CHOWN: "false"
PHOTOPRISM_DISABLE_WEBDAV: "false"
PHOTOPRISM_DISABLE_SETTINGS: "false"
PHOTOPRISM_DISABLE_TENSORFLOW: "false"
PHOTOPRISM_DISABLE_FACES: "false"
PHOTOPRISM_DISABLE_CLASSIFICATION: "false"
PHOTOPRISM_DISABLE_VECTORS: "false"
PHOTOPRISM_DISABLE_RAW: "false"
PHOTOPRISM_RAW_PRESETS: "false"
PHOTOPRISM_SIDECAR_YAML: "true"
PHOTOPRISM_BACKUP_ALBUMS: "true"
PHOTOPRISM_BACKUP_DATABASE: "true"
PHOTOPRISM_BACKUP_SCHEDULE: "daily"
PHOTOPRISM_INDEX_SCHEDULE: ""
PHOTOPRISM_AUTO_INDEX: 300
PHOTOPRISM_AUTO_IMPORT: -1
PHOTOPRISM_DETECT_NSFW: "false"
PHOTOPRISM_UPLOAD_NSFW: "true"
PHOTOPRISM_DATABASE_DRIVER: "mysql"
PHOTOPRISM_DATABASE_SERVER: "mariadb:3306"
PHOTOPRISM_DATABASE_NAME: "photoprism"
PHOTOPRISM_DATABASE_USER: "photoprism"
PHOTOPRISM_DATABASE_PASSWORD: "insecure"
PHOTOPRISM_SITE_CAPTION: "AI-Powered Photos App"
PHOTOPRISM_SITE_DESCRIPTION: ""
PHOTOPRISM_SITE_AUTHOR: ""
working_dir:
"/photoprism"
volumes:
- pictures:/photoprism/originals
- storage-data:/photoprism/storage
mariadb:
image: mariadb:11
restart: unless-stopped
stop_grace_period: 5s
networks:
- dokploy-network
security_opt:
- seccomp:unconfined
- apparmor:unconfined
volumes:
- db-data:/var/lib/mysql
environment:
MARIADB_AUTO_UPGRADE: "1"
MARIADB_INITDB_SKIP_TZINFO: "1"
MARIADB_DATABASE: "photoprism"
MARIADB_USER: "photoprism"
MARIADB_PASSWORD: "insecure"
MARIADB_ROOT_PASSWORD: "insecure"
volumes:
db-data:
storage-data:
pictures:

View File

@@ -0,0 +1,31 @@
import {
type DomainSchema,
type Schema,
type Template,
generateHash,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const randomDomain = generateRandomDomain(schema);
const randomPassword = generatePassword();
const domains: DomainSchema[] = [
{
host: randomDomain,
port: 2342,
serviceName: "photoprism",
},
];
const envs = [
`BASE_URL=http://${randomDomain}`,
`ADMIN_PASSWORD=${randomPassword}`,
];
return {
envs,
domains,
};
}

View File

@@ -1,7 +1,7 @@
version: "3.8"
services:
postiz:
postiz-app:
image: ghcr.io/gitroomhq/postiz-app:latest
restart: always
networks:
@@ -37,7 +37,7 @@ services:
POSTGRES_USER: ${DB_USER}
POSTGRES_DB: ${DB_NAME}
volumes:
- postgres-volume:/var/lib/postgresql/data
- postiz-postgres-data:/var/lib/postgresql/data
healthcheck:
test: pg_isready -U ${DB_USER} -d ${DB_NAME}
interval: 10s
@@ -57,8 +57,12 @@ services:
volumes:
- postiz-redis-data:/data
networks:
dokploy-network:
external: true
volumes:
postgres-volume:
postiz-postgres-data:
postiz-redis-data:
postiz-config:
postiz-uploads:

View File

@@ -18,7 +18,7 @@ export function generate(schema: Schema): Template {
{
host: mainDomain,
port: 5000,
serviceName: "postiz",
serviceName: "postiz-app",
},
];

View File

@@ -0,0 +1,39 @@
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}
- TZ=UTC
# Optional: Uncomment and set your pro key if you have one
# - SERVER_PRO_KEY=${SERVER_PRO_KEY}
depends_on:
ryot-db:
condition: service_healthy
restart: always
pull_policy: always
ryot-db:
image: postgres:16-alpine
networks:
- dokploy-network
volumes:
- ryot-postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_USER=postgres
- POSTGRES_DB=postgres
- TZ=UTC
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 10s
timeout: 5s
retries: 5
restart: unless-stopped
volumes:
ryot-postgres-data:

View File

@@ -0,0 +1,34 @@
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 adminAccessToken = generateBase64(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 8000,
serviceName: "ryot-app",
},
];
const envs = [
`POSTGRES_PASSWORD=${postgresPassword}`,
`ADMIN_ACCESS_TOKEN=${adminAccessToken}`,
"# Optional: Uncomment and set your pro key if you have one",
"# SERVER_PRO_KEY=your_pro_key_here",
];
return {
domains,
envs,
};
}

View File

@@ -1,26 +1,26 @@
version: "3.8"
services:
slash:
slash-app:
image: yourselfhosted/slash:latest
networks:
- dokploy-network
volumes:
- slash_data:/var/opt/slash
- slash-app-data:/var/opt/slash
environment:
- SLASH_DRIVER=postgres
- SLASH_DSN=postgresql://${DB_USER}:${DB_PASSWORD}@db:5432/${DB_NAME}?sslmode=disable
- SLASH_DSN=postgresql://${DB_USER}:${DB_PASSWORD}@slash-postgres:5432/${DB_NAME}?sslmode=disable
depends_on:
db:
slash-postgres:
condition: service_healthy
restart: unless-stopped
db:
slash-postgres:
image: postgres:16-alpine
networks:
- dokploy-network
volumes:
- postgres_data:/var/lib/postgresql/data
- slash-postgres-data:/var/lib/postgresql/data
environment:
- POSTGRES_USER=${DB_USER}
- POSTGRES_PASSWORD=${DB_PASSWORD}
@@ -32,6 +32,10 @@ services:
retries: 5
restart: unless-stopped
networks:
dokploy-network:
external: true
volumes:
slash_data:
postgres_data:
slash-app-data:
slash-postgres-data:

View File

@@ -16,7 +16,7 @@ export function generate(schema: Schema): Template {
{
host: mainDomain,
port: 5231,
serviceName: "slash",
serviceName: "slash-app",
},
];

View File

@@ -1,12 +1,22 @@
version: "3"
services:
soketi:
image: quay.io/soketi/soketi:1.4-16-debian
container_name: soketi
stirling-pdf:
image: frooodle/s-pdf:latest
ports:
- 8080
volumes:
- stirling_pdf_trainingdata:/usr/share/tessdata
- stirling_pdf_extraconfigs:/configs
- stirling_pdf_customfiles:/customFiles/
- stirling_pdf_logs:/logs/
- stirling_pdf_pipeline:/pipeline/
environment:
SOKETI_DEBUG: "1"
SOKETI_HOST: "0.0.0.0"
SOKETI_PORT: "6001"
SOKETI_METRICS_SERVER_PORT: "9601"
restart: unless-stopped
- DOCKER_ENABLE_SECURITY=false
- INSTALL_BOOK_AND_ADVANCED_HTML_OPS=false
- LANGS=en_GB
volumes:
stirling_pdf_trainingdata:
stirling_pdf_extraconfigs:
stirling_pdf_customfiles:
stirling_pdf_logs:
stirling_pdf_pipeline:

View File

@@ -771,8 +771,8 @@ export const templates: TemplateData[] = [
logo: "postiz.png",
links: {
github: "https://github.com/gitroomhq/postiz",
website: "https://postiz.io",
docs: "https://docs.postiz.io",
website: "https://postiz.com",
docs: "https://docs.postiz.com",
},
tags: ["cms", "content-management", "publishing"],
load: () => import("./postiz/index").then((m) => m.generate),
@@ -837,4 +837,139 @@ export const templates: TemplateData[] = [
tags: ["3d", "rendering", "animation"],
load: () => import("./blender/index").then((m) => m.generate),
},
{
id: "heyform",
name: "HeyForm",
version: "latest",
description:
"Allows anyone to create engaging conversational forms for surveys, questionnaires, quizzes, and polls. No coding skills required.",
logo: "heyform.svg",
links: {
github: "https://github.com/heyform/heyform",
website: "https://heyform.net",
docs: "https://docs.heyform.net",
},
tags: ["form", "builder", "questionnaire", "quiz", "survey"],
load: () => import("./heyform/index").then((m) => m.generate),
},
{
id: "chatwoot",
name: "Chatwoot",
version: "v3.14.1",
description:
"Open-source customer engagement platform that provides a shared inbox for teams, live chat, and omnichannel support.",
logo: "chatwoot.svg",
links: {
github: "https://github.com/chatwoot/chatwoot",
website: "https://www.chatwoot.com",
docs: "https://www.chatwoot.com/docs",
},
tags: ["support", "chat", "customer-service"],
load: () => import("./chatwoot/index").then((m) => m.generate),
},
{
id: "discourse",
name: "Discourse",
version: "3.3.2",
description:
"Discourse is a modern forum software for your community. Use it as a mailing list, discussion forum, or long-form chat room.",
logo: "discourse.svg",
links: {
github: "https://github.com/discourse/discourse",
website: "https://www.discourse.org/",
docs: "https://meta.discourse.org/",
},
tags: ["forum", "community", "discussion"],
load: () => import("./discourse/index").then((m) => m.generate),
},
{
id: "immich",
name: "Immich",
version: "v1.121.0",
description:
"High performance self-hosted photo and video backup solution directly from your mobile phone.",
logo: "immich.svg",
links: {
github: "https://github.com/immich-app/immich",
website: "https://immich.app/",
docs: "https://immich.app/docs/overview/introduction",
},
tags: ["photos", "videos", "backup", "media"],
load: () => import("./immich/index").then((m) => m.generate),
},
{
id: "twenty",
name: "Twenty CRM",
version: "latest",
description:
"Twenty is a modern CRM offering a powerful spreadsheet interface and open-source alternative to Salesforce.",
logo: "twenty.svg",
links: {
github: "https://github.com/twentyhq/twenty",
website: "https://twenty.com",
docs: "https://docs.twenty.com",
},
tags: ["crm", "sales", "business"],
load: () => import("./twenty/index").then((m) => m.generate),
},
{
id: "yourls",
name: "YOURLS",
version: "1.9.2",
description:
"YOURLS (Your Own URL Shortener) is a set of PHP scripts that will allow you to run your own URL shortening service (a la TinyURL or Bitly).",
logo: "yourls.svg",
links: {
github: "https://github.com/YOURLS/YOURLS",
website: "https://yourls.org/",
docs: "https://yourls.org/#documentation",
},
tags: ["url-shortener", "php"],
load: () => import("./yourls/index").then((m) => m.generate),
},
{
id: "ryot",
name: "Ryot",
version: "v7.10",
description:
"A self-hosted platform for tracking various media types including movies, TV shows, video games, books, audiobooks, and more.",
logo: "ryot.png",
links: {
github: "https://github.com/IgnisDa/ryot",
website: "https://ryot.dev/",
docs: "https://ryot.dev/docs/getting-started",
},
tags: ["media", "tracking", "self-hosted"],
load: () => import("./ryot/index").then((m) => m.generate),
},
{
id: "photoprism",
name: "Photoprism",
version: "latest",
description:
"PhotoPrism® is an AI-Powered Photos App for the Decentralized Web. It makes use of the latest technologies to tag and find pictures automatically without getting in your way.",
logo: "photoprism.svg",
links: {
github: "https://github.com/photoprism/photoprism",
website: "https://www.photoprism.app/",
docs: "https://docs.photoprism.app/",
},
tags: ["media", "photos", "self-hosted"],
load: () => import("./photoprism/index").then((m) => m.generate),
},
{
id: "ontime",
name: "Ontime",
version: "v3.8.0",
description:
"Ontime is browser-based application that manages event rundowns, scheduliing and cuing",
logo: "ontime.png",
links: {
github: "https://github.com/cpvalente/ontime/",
website: "https://getontime.no",
docs: "https://docs.getontime.no",
},
tags: ["event"],
load: () => import("./ontime/index").then((m) => m.generate),
},
];

View File

@@ -0,0 +1,104 @@
version: "3.9"
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
command: >
bash -c "
chown -R 1000:1000 /tmp/server-local-data
&& chown -R 1000:1000 /tmp/docker-data"
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
environment:
PORT: 3000
PG_DATABASE_URL: postgres://${DB_USER}:${DB_PASSWORD}@twenty-postgres:5432/twenty
SERVER_URL: https://${TWENTY_HOST}
FRONT_BASE_URL: https://${TWENTY_HOST}
REDIS_URL: redis://twenty-redis:6379
ENABLE_DB_MIGRATIONS: "true"
SIGN_IN_PREFILLED: "true"
STORAGE_TYPE: local
APP_SECRET: ${APP_SECRET}
depends_on:
twenty-change-vol-ownership:
condition: service_completed_successfully
twenty-postgres:
condition: service_healthy
healthcheck:
test: curl --fail http://localhost:3000/healthz
interval: 5s
timeout: 5s
retries: 10
restart: always
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
SERVER_URL: https://${TWENTY_HOST}
FRONT_BASE_URL: https://${TWENTY_HOST}
REDIS_URL: redis://twenty-redis:6379
ENABLE_DB_MIGRATIONS: "false"
STORAGE_TYPE: local
APP_SECRET: ${APP_SECRET}
depends_on:
twenty-postgres:
condition: service_healthy
twenty-server:
condition: service_healthy
restart: always
twenty-postgres:
image: postgres:16-alpine
networks:
- dokploy-network
volumes:
- twenty-postgres-data:/var/lib/postgresql/data
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: twenty
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${DB_USER} -d twenty"]
interval: 5s
timeout: 5s
retries: 10
restart: always
twenty-redis:
image: redis:latest
networks:
- dokploy-network
volumes:
- twenty-redis-data:/data
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 5s
retries: 10
restart: always
networks:
dokploy-network:
external: true
volumes:
twenty-docker-data:
twenty-postgres-data:
twenty-server-local-data:
twenty-redis-data:

View File

@@ -0,0 +1,37 @@
import {
type DomainSchema,
type Schema,
type Template,
generateBase64,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const dbPassword = generatePassword();
const dbUser = "twenty";
const appSecret = generateBase64(32);
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 3000,
serviceName: "twenty-server",
},
];
const envs = [
`TWENTY_HOST=${mainDomain}`,
`DB_USER=${dbUser}`,
`DB_PASSWORD=${dbPassword}`,
`APP_SECRET=${appSecret}`,
"# Optional: Configure storage path",
"# STORAGE_LOCAL_PATH=.local-storage",
];
return {
domains,
envs,
};
}

View File

@@ -1,12 +1,12 @@
version: "3.8"
services:
db:
windmill-postgres:
image: postgres:16
shm_size: 1g
restart: unless-stopped
volumes:
- db_data:/var/lib/postgresql/data
- windmill-postgres-data:/var/lib/postgresql/data
networks:
- dokploy-network
environment:
@@ -18,7 +18,7 @@ services:
timeout: 5s
retries: 5
windmill_server:
windmill-server:
image: ghcr.io/windmill-labs/windmill:main
networks:
- dokploy-network
@@ -28,12 +28,12 @@ services:
- MODE=server
- BASE_URL=http://${WINDMILL_HOST}
depends_on:
db:
windmill-postgres:
condition: service_healthy
volumes:
- worker_logs:/tmp/windmill/logs
- windmill-worker-logs:/tmp/windmill/logs
windmill_worker:
windmill-worker:
image: ghcr.io/windmill-labs/windmill:main
deploy:
replicas: 3
@@ -49,14 +49,14 @@ services:
- MODE=worker
- WORKER_GROUP=default
depends_on:
db:
windmill-postgres:
condition: service_healthy
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- worker_dependency_cache:/tmp/windmill/cache
- worker_logs:/tmp/windmill/logs
- windmill-worker-cache:/tmp/windmill/cache
- windmill-worker-logs:/tmp/windmill/logs
windmill_worker_native:
windmill-worker-native:
image: ghcr.io/windmill-labs/windmill:main
deploy:
replicas: 1
@@ -74,20 +74,20 @@ services:
- NUM_WORKERS=8
- SLEEP_QUEUE=200
depends_on:
db:
windmill-postgres:
condition: service_healthy
volumes:
- worker_logs:/tmp/windmill/logs
- windmill-worker-logs:/tmp/windmill/logs
lsp:
windmill-lsp:
image: ghcr.io/windmill-labs/windmill-lsp:latest
restart: unless-stopped
networks:
- dokploy-network
volumes:
- lsp_cache:/root/.cache
- windmill-lsp-cache:/root/.cache
caddy:
windmill-caddy:
image: ghcr.io/windmill-labs/caddy-l4:latest
restart: unless-stopped
networks:
@@ -97,11 +97,15 @@ services:
environment:
- BASE_URL=":80"
depends_on:
- windmill_server
- lsp
- windmill-server
- windmill-lsp
networks:
dokploy-network:
external: true
volumes:
db_data:
worker_dependency_cache:
worker_logs:
lsp_cache:
windmill-postgres-data:
windmill-worker-cache:
windmill-worker-logs:
windmill-lsp-cache:

View File

@@ -14,14 +14,14 @@ export function generate(schema: Schema): Template {
{
host: mainDomain,
port: 80,
serviceName: "caddy",
serviceName: "windmill-caddy",
},
];
const envs = [
`WINDMILL_HOST=${mainDomain}`,
`POSTGRES_PASSWORD=${postgresPassword}`,
`DATABASE_URL=postgres://postgres:${postgresPassword}@db/windmill?sslmode=disable`,
`DATABASE_URL=postgres://postgres:${postgresPassword}@windmill-postgres/windmill?sslmode=disable`,
];
const mounts: Template["mounts"] = [
@@ -29,8 +29,8 @@ export function generate(schema: Schema): Template {
filePath: "Caddyfile",
content: `:80 {
bind 0.0.0.0
reverse_proxy /ws/* http://lsp:3001
reverse_proxy /* http://windmill_server:8000
reverse_proxy /ws/* http://windmill-lsp:3001
reverse_proxy /* http://windmill-server:8000
}`,
},
];

View File

@@ -0,0 +1,43 @@
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}
YOURLS_PASS: ${YOURLS_ADMIN_PASSWORD}
YOURLS_DB_HOST: yourls-mysql
YOURLS_DB_USER: yourls
YOURLS_DB_PASS: ${MYSQL_PASSWORD}
YOURLS_DB_NAME: yourls
volumes:
- yourls-data:/var/www/html
depends_on:
yourls-mysql:
condition: service_healthy
restart: always
yourls-mysql:
image: mysql:5.7
networks:
- dokploy-network
environment:
MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD}
MYSQL_DATABASE: yourls
MYSQL_USER: yourls
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
volumes:
- yourls-mysql-data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u$$MYSQL_USER", "-p$$MYSQL_PASSWORD"]
interval: 10s
timeout: 5s
retries: 5
restart: always
volumes:
yourls-data:
yourls-mysql-data:

View File

@@ -0,0 +1,35 @@
import {
type DomainSchema,
type Schema,
type Template,
generatePassword,
generateRandomDomain,
} from "../utils";
export function generate(schema: Schema): Template {
const mainDomain = generateRandomDomain(schema);
const mysqlPassword = generatePassword();
const mysqlRootPassword = generatePassword();
const adminPassword = generatePassword();
const domains: DomainSchema[] = [
{
host: mainDomain,
port: 80,
serviceName: "yourls-app",
},
];
const envs = [
`YOURLS_HOST=${mainDomain}`,
"YOURLS_ADMIN_USER=admin",
`YOURLS_ADMIN_PASSWORD=${adminPassword}`,
`MYSQL_PASSWORD=${mysqlPassword}`,
`MYSQL_ROOT_PASSWORD=${mysqlRootPassword}`,
];
return {
domains,
envs,
};
}

View File

@@ -1,6 +1,13 @@
import Cookies from "js-cookie";
const SUPPORTED_LOCALES = ["en", "pl", "zh-Hans"] as const;
const SUPPORTED_LOCALES = [
"en",
"pl",
"ru",
"de",
"zh-Hant",
"zh-Hans",
] as const;
type Locale = (typeof SUPPORTED_LOCALES)[number];