mirror of
https://github.com/Dokploy/dokploy
synced 2025-06-26 18:27:59 +00:00
Compare commits
34 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
467acc4d4d | ||
|
|
fc2778db35 | ||
|
|
85d6ff9012 | ||
|
|
522f8baec7 | ||
|
|
22eb965919 | ||
|
|
c0746b95b3 | ||
|
|
01e5cf0852 | ||
|
|
8faa6ae1cf | ||
|
|
76ed1107c2 | ||
|
|
cff5049096 | ||
|
|
cb5ca100a6 | ||
|
|
03d1e974dd | ||
|
|
b609d72d1c | ||
|
|
d7071fba60 | ||
|
|
76585991ec | ||
|
|
da6efcf733 | ||
|
|
64b0770cfb | ||
|
|
1ec83a3236 | ||
|
|
df9fad088f | ||
|
|
7d5a660f4d | ||
|
|
f7f0cbf318 | ||
|
|
841c0731aa | ||
|
|
137cd25267 | ||
|
|
4dcd16c41e | ||
|
|
60497fe59d | ||
|
|
8536945a60 | ||
|
|
e0a8d8258c | ||
|
|
fe19cdb5e4 | ||
|
|
c4654a9619 | ||
|
|
0a123a652b | ||
|
|
5d437c29b2 | ||
|
|
53f345ab1d | ||
|
|
f8721d3e04 | ||
|
|
a6c7c3b031 |
1
.husky/commit-msg
Normal file
1
.husky/commit-msg
Normal file
@@ -0,0 +1 @@
|
|||||||
|
npx commitlint --edit "$1"
|
||||||
6
.husky/install.mjs
Normal file
6
.husky/install.mjs
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
// Skip Husky install in production and CI
|
||||||
|
if (process.env.NODE_ENV === "production" || process.env.CI === "true") {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
const husky = (await import("husky")).default;
|
||||||
|
console.log(husky());
|
||||||
2
.husky/pre-commit
Normal file
2
.husky/pre-commit
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
pnpm run check
|
||||||
|
git add .
|
||||||
@@ -71,6 +71,12 @@ Run the command that will spin up all the required services and files.
|
|||||||
pnpm run dokploy:setup
|
pnpm run dokploy:setup
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Build the server package (If you make any changes after in the packages/server folder, you need to rebuild and run this command)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm run server:build
|
||||||
|
```
|
||||||
|
|
||||||
Now run the development server.
|
Now run the development server.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -17,10 +17,10 @@ See the License for the specific language governing permissions and limitations
|
|||||||
|
|
||||||
## Additional Terms for Specific Features
|
## Additional Terms for Specific Features
|
||||||
|
|
||||||
The following additional terms apply to the multi-node support and Docker Compose file support features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
|
The following additional terms apply to the multi-node support, Docker Compose file and Multi Server features of Dokploy. In the event of a conflict, these provisions shall take precedence over those in the Apache License:
|
||||||
|
|
||||||
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support and Docker Compose file support, will always be free to use in the self-hosted version.
|
- **Self-Hosted Version Free**: All features of Dokploy, including multi-node support, Docker Compose file support and Multi Server, will always be free to use in the self-hosted version.
|
||||||
- **Restriction on Resale**: The multi-node support and Docker Compose file support features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
|
- **Restriction on Resale**: The multi-node support, Docker Compose file support and Multi Server features cannot be sold or offered as a service by any party other than the copyright holder without prior written consent.
|
||||||
- **Modification Distribution**: Any modifications to the multi-node support and Docker Compose file support features must be distributed freely and cannot be sold or offered as a service.
|
- **Modification Distribution**: Any modifications to the multi-node support, Docker Compose file support and Multi Server features must be distributed freely and cannot be sold or offered as a service.
|
||||||
|
|
||||||
For further inquiries or permissions, please contact us directly.
|
For further inquiries or permissions, please contact us directly.
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Dokploy include multiples features to make your life easier.
|
|||||||
- **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
- **Traefik Integration**: Automatically integrates with Traefik for routing and load balancing.
|
||||||
- **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
|
- **Real-time Monitoring**: Monitor CPU, memory, storage, and network usage, for every resource.
|
||||||
- **Docker Management**: Easily deploy and manage Docker containers.
|
- **Docker Management**: Easily deploy and manage Docker containers.
|
||||||
- **CLI/API**: Manage your applications and databases using the command line or trought the API.
|
- **CLI/API**: Manage your applications and databases using the command line or through the API.
|
||||||
- **Notifications**: Get notified when your deployments are successful or failed (Slack, Discord, Telegram, Email, etc.)
|
- **Notifications**: Get notified when your deployments are successful or failed (Slack, Discord, Telegram, Email, etc.)
|
||||||
- **Multi Server**: Deploy and manager your applications remotely to external servers.
|
- **Multi Server**: Deploy and manager your applications remotely to external servers.
|
||||||
- **Self-Hosted**: Self-host Dokploy on your VPS.
|
- **Self-Hosted**: Self-host Dokploy on your VPS.
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "dokploy",
|
"name": "dokploy",
|
||||||
"version": "v0.10.0",
|
"version": "v0.10.3",
|
||||||
"private": true,
|
"private": true,
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
@@ -10,7 +10,7 @@
|
|||||||
"build-server": "tsx esbuild.config.ts",
|
"build-server": "tsx esbuild.config.ts",
|
||||||
"build-next": "next build",
|
"build-next": "next build",
|
||||||
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
|
"setup": "tsx -r dotenv/config setup.ts && sleep 5 && pnpm run migration:run",
|
||||||
"reset-password": "node dist/reset-password.mjs",
|
"reset-password": "node -r dotenv/config dist/reset-password.mjs",
|
||||||
"dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
|
"dev": "tsx -r dotenv/config ./server/server.ts --project tsconfig.server.json ",
|
||||||
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
"studio": "drizzle-kit studio --config ./server/db/drizzle.config.ts",
|
||||||
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
|
"migration:generate": "drizzle-kit generate --config ./server/db/drizzle.config.ts",
|
||||||
|
|||||||
@@ -178,7 +178,7 @@ export default function Home() {
|
|||||||
<div className="mt-4 text-sm flex flex-row justify-center gap-2">
|
<div className="mt-4 text-sm flex flex-row justify-center gap-2">
|
||||||
<Link
|
<Link
|
||||||
className="hover:underline text-muted-foreground"
|
className="hover:underline text-muted-foreground"
|
||||||
href="https://docs.dokploy.com/get-started/reset-password"
|
href="https://docs.dokploy.com/docs/core/get-started/reset-password"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
>
|
>
|
||||||
Lost your password?
|
Lost your password?
|
||||||
|
|||||||
@@ -2,14 +2,16 @@ import {
|
|||||||
createDefaultMiddlewares,
|
createDefaultMiddlewares,
|
||||||
createDefaultServerTraefikConfig,
|
createDefaultServerTraefikConfig,
|
||||||
createDefaultTraefikConfig,
|
createDefaultTraefikConfig,
|
||||||
initializeNetwork,
|
|
||||||
initializePostgres,
|
|
||||||
initializeRedis,
|
|
||||||
initializeSwarm,
|
|
||||||
initializeTraefik,
|
initializeTraefik,
|
||||||
setupDirectories,
|
} from "@dokploy/server/dist/setup/traefik-setup";
|
||||||
} from "@dokploy/server";
|
|
||||||
|
|
||||||
|
import { setupDirectories } from "@dokploy/server/dist/setup/config-paths";
|
||||||
|
import { initializePostgres } from "@dokploy/server/dist/setup/postgres-setup";
|
||||||
|
import { initializeRedis } from "@dokploy/server/dist/setup/redis-setup";
|
||||||
|
import {
|
||||||
|
initializeNetwork,
|
||||||
|
initializeSwarm,
|
||||||
|
} from "@dokploy/server/dist/setup/setup";
|
||||||
(async () => {
|
(async () => {
|
||||||
try {
|
try {
|
||||||
setupDirectories();
|
setupDirectories();
|
||||||
|
|||||||
@@ -1,47 +1,66 @@
|
|||||||
version: "3.8"
|
|
||||||
services:
|
services:
|
||||||
database:
|
database:
|
||||||
image: postgis/postgis:13-master
|
image: postgis/postgis:13-master
|
||||||
volumes:
|
volumes:
|
||||||
- directus:/var/lib/postgresql/data
|
- directus_database:/var/lib/postgresql/data
|
||||||
networks:
|
networks:
|
||||||
- dokploy-network
|
- dokploy-network
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: "directus"
|
POSTGRES_USER: "directus"
|
||||||
POSTGRES_PASSWORD: "directus"
|
POSTGRES_PASSWORD: ${DATABASE_PASSWORD}
|
||||||
POSTGRES_DB: "directus"
|
POSTGRES_DB: "directus"
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "pg_isready", "--host=localhost", "--username=directus"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_interval: 5s
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
cache:
|
cache:
|
||||||
image: redis:6
|
image: redis:6
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "[ $$(redis-cli ping) = 'PONG' ]"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
start_interval: 5s
|
||||||
|
start_period: 30s
|
||||||
networks:
|
networks:
|
||||||
- dokploy-network
|
- dokploy-network
|
||||||
|
|
||||||
directus:
|
directus:
|
||||||
image: directus/directus:10.12.1
|
image: directus/directus:11.0.2
|
||||||
ports:
|
ports:
|
||||||
- 8055
|
- 8055
|
||||||
volumes:
|
volumes:
|
||||||
- ../files/uploads:/directus/uploads
|
- directus_uploads:/directus/uploads
|
||||||
- ../files/extensions:/directus/extensions
|
- directus_extensions:/directus/extensions
|
||||||
depends_on:
|
depends_on:
|
||||||
- cache
|
database:
|
||||||
- database
|
condition: service_healthy
|
||||||
|
cache:
|
||||||
|
condition: service_healthy
|
||||||
environment:
|
environment:
|
||||||
SECRET: "replace-with-secure-random-value"
|
SECRET: ${DIRECTUS_SECRET}
|
||||||
|
|
||||||
DB_CLIENT: "pg"
|
DB_CLIENT: "pg"
|
||||||
DB_HOST: "database"
|
DB_HOST: "database"
|
||||||
DB_PORT: "5432"
|
DB_PORT: "5432"
|
||||||
DB_DATABASE: "directus"
|
DB_DATABASE: "directus"
|
||||||
DB_USER: "directus"
|
DB_USER: "directus"
|
||||||
DB_PASSWORD: "directus"
|
DB_PASSWORD: ${DATABASE_PASSWORD}
|
||||||
|
|
||||||
CACHE_ENABLED: "true"
|
CACHE_ENABLED: "true"
|
||||||
CACHE_AUTO_PURGE: "true"
|
CACHE_AUTO_PURGE: "true"
|
||||||
CACHE_STORE: "redis"
|
CACHE_STORE: "redis"
|
||||||
REDIS: "redis://cache:6379"
|
REDIS: "redis://cache:6379"
|
||||||
|
|
||||||
|
# After first successful login, remove the admin email/password env. variables below
|
||||||
|
# as these will now be stored in the database.
|
||||||
ADMIN_EMAIL: "admin@example.com"
|
ADMIN_EMAIL: "admin@example.com"
|
||||||
ADMIN_PASSWORD: "d1r3ctu5"
|
ADMIN_PASSWORD: "d1r3ctu5"
|
||||||
volumes:
|
volumes:
|
||||||
directus:
|
directus_uploads:
|
||||||
|
directus_extensions:
|
||||||
|
directus_database:
|
||||||
@@ -2,10 +2,15 @@ import {
|
|||||||
type DomainSchema,
|
type DomainSchema,
|
||||||
type Schema,
|
type Schema,
|
||||||
type Template,
|
type Template,
|
||||||
|
generateBase64,
|
||||||
|
generatePassword,
|
||||||
generateRandomDomain,
|
generateRandomDomain,
|
||||||
} from "../utils";
|
} from "../utils";
|
||||||
|
|
||||||
export function generate(schema: Schema): Template {
|
export function generate(schema: Schema): Template {
|
||||||
|
const directusSecret = generateBase64(64);
|
||||||
|
const databasePassword = generatePassword();
|
||||||
|
|
||||||
const domains: DomainSchema[] = [
|
const domains: DomainSchema[] = [
|
||||||
{
|
{
|
||||||
host: generateRandomDomain(schema),
|
host: generateRandomDomain(schema),
|
||||||
@@ -14,7 +19,13 @@ export function generate(schema: Schema): Template {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const envs = [
|
||||||
|
`DATABASE_PASSWORD=${databasePassword}`,
|
||||||
|
`DIRECTUS_SECRET=${directusSecret}`,
|
||||||
|
];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
domains,
|
domains,
|
||||||
|
envs,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ export const templates: TemplateData[] = [
|
|||||||
{
|
{
|
||||||
id: "directus",
|
id: "directus",
|
||||||
name: "Directus",
|
name: "Directus",
|
||||||
version: "10.12.1",
|
version: "11.0.2",
|
||||||
description:
|
description:
|
||||||
"Directus is an open source headless CMS that provides an API-first solution for building custom backends.",
|
"Directus is an open source headless CMS that provides an API-first solution for building custom backends.",
|
||||||
logo: "directus.jpg",
|
logo: "directus.jpg",
|
||||||
|
|||||||
@@ -6,6 +6,8 @@ import GoogleAnalytics from "@/components/analitycs/google";
|
|||||||
import { NextIntlClientProvider } from "next-intl";
|
import { NextIntlClientProvider } from "next-intl";
|
||||||
import { getMessages } from "next-intl/server";
|
import { getMessages } from "next-intl/server";
|
||||||
|
|
||||||
|
import { Footer } from "@/components/Footer";
|
||||||
|
import { Header } from "@/components/Header";
|
||||||
import type { Metadata } from "next";
|
import type { Metadata } from "next";
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
@@ -89,7 +91,9 @@ export default async function RootLayout({
|
|||||||
<GoogleAnalytics />
|
<GoogleAnalytics />
|
||||||
<body className="flex h-full flex-col">
|
<body className="flex h-full flex-col">
|
||||||
<NextIntlClientProvider messages={messages}>
|
<NextIntlClientProvider messages={messages}>
|
||||||
|
<Header />
|
||||||
{children}
|
{children}
|
||||||
|
<Footer />
|
||||||
</NextIntlClientProvider>
|
</NextIntlClientProvider>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -1,16 +1,12 @@
|
|||||||
import { CallToAction } from "@/components/CallToAction";
|
import { CallToAction } from "@/components/CallToAction";
|
||||||
import { Faqs } from "@/components/Faqs";
|
import { Faqs } from "@/components/Faqs";
|
||||||
import { Footer } from "@/components/Footer";
|
|
||||||
import { Header } from "@/components/Header";
|
|
||||||
import { Hero } from "@/components/Hero";
|
import { Hero } from "@/components/Hero";
|
||||||
import { PrimaryFeatures } from "@/components/PrimaryFeatures";
|
import { PrimaryFeatures } from "@/components/PrimaryFeatures";
|
||||||
import { SecondaryFeatures } from "@/components/SecondaryFeatures";
|
import { SecondaryFeatures } from "@/components/SecondaryFeatures";
|
||||||
import { Testimonials } from "../../components/Testimonials";
|
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Header />
|
|
||||||
<main>
|
<main>
|
||||||
<Hero />
|
<Hero />
|
||||||
<PrimaryFeatures />
|
<PrimaryFeatures />
|
||||||
@@ -18,7 +14,6 @@ export default function Home() {
|
|||||||
<CallToAction />
|
<CallToAction />
|
||||||
{/* <Testimonials /> */}
|
{/* <Testimonials /> */}
|
||||||
<Faqs />
|
<Faqs />
|
||||||
<Footer />
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
9
apps/website/app/[locale]/pricing/page.tsx
Normal file
9
apps/website/app/[locale]/pricing/page.tsx
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
import { Pricing } from "@/components/pricing";
|
||||||
|
|
||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
<Pricing />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
111
apps/website/app/[locale]/privacy/page.tsx
Normal file
111
apps/website/app/[locale]/privacy/page.tsx
Normal file
@@ -0,0 +1,111 @@
|
|||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 w-full max-w-4xl mx-auto">
|
||||||
|
<h1 className="text-3xl font-bold text-center mb-6">Privacy</h1>
|
||||||
|
|
||||||
|
<section className="flex flex-col gap-2">
|
||||||
|
<p>
|
||||||
|
At Dokploy, we are committed to protecting your privacy. This Privacy
|
||||||
|
Policy explains how we collect, use, and safeguard your personal
|
||||||
|
information when you use our website and services.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
By using Dokploy, you agree to the collection and use of information
|
||||||
|
in accordance with this Privacy Policy. If you do not agree with these
|
||||||
|
practices, please do not use our services.
|
||||||
|
</p>
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">
|
||||||
|
1. Information We Collect
|
||||||
|
</h2>
|
||||||
|
<p className="">
|
||||||
|
We only collect limited, non-personal data through Umami Analytics, a
|
||||||
|
privacy-focused analytics tool. No personal identifying information
|
||||||
|
(PII) is collected. The data we collect includes:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside mb-4">
|
||||||
|
<li>Website usage statistics (e.g., page views, session duration)</li>
|
||||||
|
<li>Anonymized IP addresses</li>
|
||||||
|
<li>Referring websites</li>
|
||||||
|
<li>Browser and device type</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">
|
||||||
|
2. How We Use the Information
|
||||||
|
</h2>
|
||||||
|
<p className="mb-4">
|
||||||
|
The information we collect is used solely for improving the
|
||||||
|
functionality and user experience of our platform. Specifically, we
|
||||||
|
use it to:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc list-inside mb-4">
|
||||||
|
<li>Monitor traffic and website performance</li>
|
||||||
|
<li>Optimize the user experience</li>
|
||||||
|
<li>Understand how users interact with our platform</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Additionally, we use a single cookie to manage user sessions, which is
|
||||||
|
necessary for the proper functioning of the platform.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="flex flex-col gap-2">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">3. Data Security</h2>
|
||||||
|
<p className="">
|
||||||
|
We take reasonable precautions to protect your data. Since we do not
|
||||||
|
collect personal information, the risk of data misuse is minimized.
|
||||||
|
Umami Analytics is privacy-friendly and does not rely on cookies or
|
||||||
|
store PII.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">4. Third-Party Services</h2>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
We do not share your data with any third-party services other than
|
||||||
|
Umami Analytics. We do not sell, trade, or transfer your data to
|
||||||
|
outside parties.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">5. Cookies</h2>
|
||||||
|
<p className="mb-4">
|
||||||
|
Dokploy does not use cookies to track user activity. Umami Analytics
|
||||||
|
is cookie-free and does not require any tracking cookies for its
|
||||||
|
functionality.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="flex flex-col gap-2">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">
|
||||||
|
6. Changes to This Privacy Policy
|
||||||
|
</h2>
|
||||||
|
<p className="">
|
||||||
|
We may update this Privacy Policy from time to time. Any changes will
|
||||||
|
be posted on this page, and it is your responsibility to review this
|
||||||
|
policy periodically.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">12. Contact Information</h2>
|
||||||
|
<p className="mb-4">
|
||||||
|
If you have any questions or concerns regarding these Privacy Policy,
|
||||||
|
please contact us at:
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Email:
|
||||||
|
<a
|
||||||
|
href="mailto:support@dokploy.com"
|
||||||
|
className="text-blue-500 hover:underline"
|
||||||
|
>
|
||||||
|
support@dokploy.com
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
205
apps/website/app/[locale]/terms/page.tsx
Normal file
205
apps/website/app/[locale]/terms/page.tsx
Normal file
@@ -0,0 +1,205 @@
|
|||||||
|
export default function Home() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col gap-4 w-full max-w-4xl mx-auto">
|
||||||
|
<h1 className="text-3xl font-bold text-center mb-6">
|
||||||
|
Terms and Conditions
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<section className="flex flex-col gap-2">
|
||||||
|
<p>
|
||||||
|
Welcome to Dokploy! These Terms and Conditions outline the rules and
|
||||||
|
regulations for the use of Dokploy’s website and services.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
By accessing or using our services, you agree to be bound by the
|
||||||
|
following terms. If you do not agree with these terms, please do not
|
||||||
|
use our website or services.
|
||||||
|
</p>
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">1. Definitions</h2>
|
||||||
|
<p className="">
|
||||||
|
Website: Refers to the website of Dokploy (
|
||||||
|
<a
|
||||||
|
href="https://dokploy.com"
|
||||||
|
className="text-blue-500 hover:underline"
|
||||||
|
>
|
||||||
|
https://dokploy.com
|
||||||
|
</a>
|
||||||
|
) and its subdomains.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Services: The platform and related services offered by Dokploy for
|
||||||
|
deploying and managing applications using Docker and other related
|
||||||
|
tools.
|
||||||
|
</p>
|
||||||
|
<p>User: Any individual or organization using Dokploy.</p>
|
||||||
|
<p>
|
||||||
|
Subscription: The paid plan for using additional features, resources,
|
||||||
|
or server capacity.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">2. Service Description</h2>
|
||||||
|
<p className="mb-4">
|
||||||
|
Dokploy is a platform that allows users to deploy and manage web
|
||||||
|
applications on their own servers using custom builders and Docker
|
||||||
|
technology. Dokploy offers both free and paid services, including
|
||||||
|
subscriptions for adding additional servers, features, or increased
|
||||||
|
capacity.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="flex flex-col gap-2">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">
|
||||||
|
3. User Responsibilities
|
||||||
|
</h2>
|
||||||
|
<p className="">
|
||||||
|
Users are responsible for maintaining the security of their accounts,
|
||||||
|
servers, and applications deployed through Dokploy.
|
||||||
|
</p>
|
||||||
|
<p className="">
|
||||||
|
Users must not use the platform for illegal activities, including but
|
||||||
|
not limited to distributing malware, violating intellectual property
|
||||||
|
rights, or engaging in cyberattacks.
|
||||||
|
</p>
|
||||||
|
<p className="">
|
||||||
|
Users must comply with all local, state, and international laws in
|
||||||
|
connection with their use of Dokploy.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">
|
||||||
|
4. Subscription and Payment
|
||||||
|
</h2>
|
||||||
|
<ul className="list-disc list-inside mb-4">
|
||||||
|
<li>
|
||||||
|
By purchasing a subscription, users agree to the pricing and payment
|
||||||
|
terms detailed on the website or via Paddle (our payment processor).
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Subscriptions renew automatically unless canceled by the user before
|
||||||
|
the renewal date.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">5. Refund Policy</h2>
|
||||||
|
<p className="mb-4">
|
||||||
|
Due to the nature of our digital services, Dokploy operates on a
|
||||||
|
no-refund policy for any paid subscriptions, except where required by
|
||||||
|
law. We offer a self-hosted version of Dokploy with the same core
|
||||||
|
functionalities, which users can deploy and use without any cost. We
|
||||||
|
recommend users try the self-hosted version to evaluate the platform
|
||||||
|
before committing to a paid subscription.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="flex flex-col gap-2">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">
|
||||||
|
6. Limitations of Liability
|
||||||
|
</h2>
|
||||||
|
<p className="">
|
||||||
|
Dokploy is provided "as is" without any warranties, express or
|
||||||
|
implied, including but not limited to the availability, reliability,
|
||||||
|
or accuracy of the service.
|
||||||
|
</p>
|
||||||
|
<p className="">
|
||||||
|
Users are fully responsible for any modifications made to their remote
|
||||||
|
servers or the environment where Dokploy is deployed. Any changes to
|
||||||
|
the server configuration, system settings, security policies, or other
|
||||||
|
environments that deviate from the recommended use of Dokploy may
|
||||||
|
result in compatibility issues, performance degradation, or security
|
||||||
|
vulnerabilities. Additionally, Dokploy may not function properly on
|
||||||
|
unsupported operating systems or environments. We do not guarantee the
|
||||||
|
platform will operate correctly or reliably under modified server
|
||||||
|
conditions or on unsupported systems, and we will not be held liable
|
||||||
|
for any disruptions, malfunctions, or damages resulting from such
|
||||||
|
changes or unsupported configurations.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">
|
||||||
|
7. Service Modifications and Downtime
|
||||||
|
</h2>
|
||||||
|
<p className="mb-4">
|
||||||
|
While we strive to provide uninterrupted service, there may be periods
|
||||||
|
of downtime due to scheduled maintenance or upgrades to our
|
||||||
|
infrastructure, such as server maintenance or system improvements. We
|
||||||
|
will provide notice to users ahead of any planned maintenance.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="flex flex-col gap-2">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">
|
||||||
|
8. Intellectual Property
|
||||||
|
</h2>
|
||||||
|
<p className="">
|
||||||
|
Dokploy retains all intellectual property rights to the platform,
|
||||||
|
including code, design, and content.
|
||||||
|
</p>
|
||||||
|
<p className="">
|
||||||
|
Users are granted a limited, non-exclusive, and non-transferable
|
||||||
|
license to use Dokploy in accordance with these terms.
|
||||||
|
</p>
|
||||||
|
<p className="">
|
||||||
|
Users may not modify, reverse-engineer, or distribute any part of the
|
||||||
|
platform without express permission.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">9. Termination</h2>
|
||||||
|
<p className="mb-4">
|
||||||
|
Dokploy reserves the right to suspend or terminate access to the
|
||||||
|
platform for users who violate these terms or engage in harmful
|
||||||
|
behavior.
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Users may terminate their account at any time by contacting support.
|
||||||
|
Upon termination, access to the platform will be revoked, and any
|
||||||
|
stored data may be permanently deleted.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">10. Changes to Terms</h2>
|
||||||
|
<p className="mb-4">
|
||||||
|
Dokploy reserves the right to update these Terms & Conditions at any
|
||||||
|
time. Changes will be effective immediately upon posting on the
|
||||||
|
website. It is the user's responsibility to review these terms
|
||||||
|
periodically.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">11. Governing Law</h2>
|
||||||
|
<p className="mb-4">
|
||||||
|
These Terms & Conditions are governed by applicable laws based on the
|
||||||
|
user's location. Any disputes arising under these terms will be
|
||||||
|
resolved in accordance with the legal jurisdiction relevant to the
|
||||||
|
user’s location, unless otherwise required by applicable law.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="">
|
||||||
|
<h2 className="text-2xl font-semibold mb-4">12. Contact Information</h2>
|
||||||
|
<p className="mb-4">
|
||||||
|
If you have any questions or concerns regarding these Terms, you can
|
||||||
|
reach us at:
|
||||||
|
</p>
|
||||||
|
<p className="mb-4">
|
||||||
|
Email:
|
||||||
|
<a
|
||||||
|
href="mailto:support@dokploy.com"
|
||||||
|
className="text-blue-500 hover:underline"
|
||||||
|
>
|
||||||
|
support@dokploy.com
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -7,9 +7,6 @@ export function SlimLayout() {
|
|||||||
const t = useTranslations("404");
|
const t = useTranslations("404");
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
|
||||||
<Header />
|
|
||||||
</div>
|
|
||||||
<main className="flex flex-auto items-center justify-center text-center">
|
<main className="flex flex-auto items-center justify-center text-center">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="mb-4 text-6xl font-semibold text-primary">404</h1>
|
<h1 className="mb-4 text-6xl font-semibold text-primary">404</h1>
|
||||||
@@ -22,9 +19,6 @@ export function SlimLayout() {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<div>
|
|
||||||
<Footer />
|
|
||||||
</div>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
310
apps/website/components/pricing.tsx
Normal file
310
apps/website/components/pricing.tsx
Normal file
@@ -0,0 +1,310 @@
|
|||||||
|
"use client";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useState } from "react";
|
||||||
|
import { Container } from "./Container";
|
||||||
|
import { trackGAEvent } from "./analitycs";
|
||||||
|
import { Button } from "./ui/button";
|
||||||
|
import { Switch } from "./ui/switch";
|
||||||
|
|
||||||
|
function SwirlyDoodle(props: React.ComponentPropsWithoutRef<"svg">) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
viewBox="0 0 281 40"
|
||||||
|
preserveAspectRatio="none"
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
fillRule="evenodd"
|
||||||
|
clipRule="evenodd"
|
||||||
|
d="M240.172 22.994c-8.007 1.246-15.477 2.23-31.26 4.114-18.506 2.21-26.323 2.977-34.487 3.386-2.971.149-3.727.324-6.566 1.523-15.124 6.388-43.775 9.404-69.425 7.31-26.207-2.14-50.986-7.103-78-15.624C10.912 20.7.988 16.143.734 14.657c-.066-.381.043-.344 1.324.456 10.423 6.506 49.649 16.322 77.8 19.468 23.708 2.65 38.249 2.95 55.821 1.156 9.407-.962 24.451-3.773 25.101-4.692.074-.104.053-.155-.058-.135-1.062.195-13.863-.271-18.848-.687-16.681-1.389-28.722-4.345-38.142-9.364-15.294-8.15-7.298-19.232 14.802-20.514 16.095-.934 32.793 1.517 47.423 6.96 13.524 5.033 17.942 12.326 11.463 18.922l-.859.874.697-.006c2.681-.026 15.304-1.302 29.208-2.953 25.845-3.07 35.659-4.519 54.027-7.978 9.863-1.858 11.021-2.048 13.055-2.145a61.901 61.901 0 0 0 4.506-.417c1.891-.259 2.151-.267 1.543-.047-.402.145-2.33.913-4.285 1.707-4.635 1.882-5.202 2.07-8.736 2.903-3.414.805-19.773 3.797-26.404 4.829Zm40.321-9.93c.1-.066.231-.085.29-.041.059.043-.024.096-.183.119-.177.024-.219-.007-.107-.079ZM172.299 26.22c9.364-6.058 5.161-12.039-12.304-17.51-11.656-3.653-23.145-5.47-35.243-5.576-22.552-.198-33.577 7.462-21.321 14.814 12.012 7.205 32.994 10.557 61.531 9.831 4.563-.116 5.372-.288 7.337-1.559Z"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function CheckIcon({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentPropsWithoutRef<"svg">) {
|
||||||
|
return (
|
||||||
|
<svg
|
||||||
|
aria-hidden="true"
|
||||||
|
className={clsx(
|
||||||
|
"h-6 w-6 flex-none fill-current stroke-current",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M9.307 12.248a.75.75 0 1 0-1.114 1.004l1.114-1.004ZM11 15.25l-.557.502a.75.75 0 0 0 1.15-.043L11 15.25Zm4.844-5.041a.75.75 0 0 0-1.188-.918l1.188.918Zm-7.651 3.043 2.25 2.5 1.114-1.004-2.25-2.5-1.114 1.004Zm3.4 2.457 4.25-5.5-1.187-.918-4.25 5.5 1.188.918Z"
|
||||||
|
strokeWidth={0}
|
||||||
|
/>
|
||||||
|
<circle
|
||||||
|
cx={12}
|
||||||
|
cy={12}
|
||||||
|
r={8.25}
|
||||||
|
fill="none"
|
||||||
|
strokeWidth={1.5}
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function Plan({
|
||||||
|
name,
|
||||||
|
price,
|
||||||
|
description,
|
||||||
|
href,
|
||||||
|
features,
|
||||||
|
featured = false,
|
||||||
|
}: {
|
||||||
|
name: string;
|
||||||
|
price: string;
|
||||||
|
description: string;
|
||||||
|
href: string;
|
||||||
|
features: Array<string>;
|
||||||
|
featured?: boolean;
|
||||||
|
}) {
|
||||||
|
const router = useRouter();
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
className={clsx(
|
||||||
|
"flex flex-col rounded-3xl px-6 sm:px-8",
|
||||||
|
featured ? "order-first bg-black border py-8 lg:order-none" : "lg:py-8",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<h3 className="mt-5 font-display text-lg text-white">{name}</h3>
|
||||||
|
<p
|
||||||
|
className={clsx(
|
||||||
|
"mt-2 text-base",
|
||||||
|
featured ? "text-white" : "text-slate-400",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{description}
|
||||||
|
</p>
|
||||||
|
<p className="order-first font-display text-5xl font-light tracking-tight text-white">
|
||||||
|
{price}
|
||||||
|
</p>
|
||||||
|
<ul
|
||||||
|
role="list"
|
||||||
|
className={clsx(
|
||||||
|
"order-last mt-10 flex flex-col gap-y-3 text-sm",
|
||||||
|
featured ? "text-white" : "text-slate-200",
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{features.map((feature) => (
|
||||||
|
<li key={feature} className="flex">
|
||||||
|
<CheckIcon className={featured ? "text-white" : "text-slate-400"} />
|
||||||
|
<span className="ml-4">{feature}</span>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
<Button
|
||||||
|
onClick={() => {
|
||||||
|
router.push(href);
|
||||||
|
trackGAEvent({
|
||||||
|
action: "Buy Plan Clicked",
|
||||||
|
category: "Pricing",
|
||||||
|
label: `${name} - ${price}`,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
className="rounded-full mt-8"
|
||||||
|
>
|
||||||
|
Get started
|
||||||
|
</Button>
|
||||||
|
{/* <Button
|
||||||
|
href={href}
|
||||||
|
variant={featured ? "solid" : "outline"}
|
||||||
|
color="white"
|
||||||
|
className="mt-8"
|
||||||
|
aria-label={`Get started with the ${name} plan for ${price}`}
|
||||||
|
>
|
||||||
|
Get started
|
||||||
|
</Button> */}
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function Pricing() {
|
||||||
|
const [monthly, setMonthly] = useState(false);
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
id="pricing"
|
||||||
|
aria-label="Pricing"
|
||||||
|
className="bg-black border-t border-border/30 py-20 sm:py-32"
|
||||||
|
>
|
||||||
|
<Container>
|
||||||
|
<div className="text-center">
|
||||||
|
<h2 className="font-display text-3xl tracking-tight text-white sm:text-4xl">
|
||||||
|
<span className="relative whitespace-nowrap">
|
||||||
|
<SwirlyDoodle className="absolute left-0 top-1/2 h-[1em] w-full fill-muted-foreground" />
|
||||||
|
<span className="relative"> Simple & Affordable,</span>
|
||||||
|
</span>{" "}
|
||||||
|
Pricing.
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-lg text-muted-foreground">
|
||||||
|
Deploy Smarter, Scale Faster – Without Breaking the Bank
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div className="mt-10 flex flex-row gap-x-4 justify-center">
|
||||||
|
<Switch checked={monthly} onCheckedChange={(e) => setMonthly(e)} />
|
||||||
|
{!monthly ? "Monthly" : "Yearly"}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className=" mt-10 mx-auto">
|
||||||
|
<div className="mt-16 grid md:grid-cols-2 gap-y-10 mx-auto w-full lg:-mx-8 xl:mx-0 xl:gap-x-8">
|
||||||
|
<Plan
|
||||||
|
name="Free"
|
||||||
|
price="$0"
|
||||||
|
description="Perfect for developers who prefer to manage their own servers."
|
||||||
|
href="https://docs.dokploy.com/en/docs/core/get-started/installation#docker"
|
||||||
|
features={[
|
||||||
|
"Unlimited deployments",
|
||||||
|
"Self-hosted on your own infrastructure",
|
||||||
|
"Full access to all deployment features",
|
||||||
|
"Docker Swarm and Docker Compose support",
|
||||||
|
"Community support",
|
||||||
|
"Custom domains and SSL integration",
|
||||||
|
"No feature limitations on the core platform",
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
<Plan
|
||||||
|
featured
|
||||||
|
name="General"
|
||||||
|
price={!monthly ? "$6.99" : "$5.49"}
|
||||||
|
description="Ideal for indie hackers, freelancers, agencies, and businesses looking for a managed solution."
|
||||||
|
href="/register"
|
||||||
|
features={[
|
||||||
|
"1 free server included (user-provided)",
|
||||||
|
"All self-hosted features without hosting the UI",
|
||||||
|
"Dokploy infrastructure managed by us",
|
||||||
|
"$3.99 per additional server (user-provided)",
|
||||||
|
]}
|
||||||
|
/>
|
||||||
|
{/* <Plan
|
||||||
|
name="Enterprise"
|
||||||
|
price="$39"
|
||||||
|
description="For even the biggest enterprise companies."
|
||||||
|
href="/register"
|
||||||
|
features={[
|
||||||
|
"Send unlimited quotes and invoices",
|
||||||
|
"Connect up to 15 bank accounts",
|
||||||
|
"Track up to 200 expenses per month",
|
||||||
|
"Automated payroll support",
|
||||||
|
"Export up to 25 reports, including TPS",
|
||||||
|
]}
|
||||||
|
/> */}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
<Faqs />
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const faqs = [
|
||||||
|
[
|
||||||
|
{
|
||||||
|
question: "How does Dokploy's free plan work?",
|
||||||
|
answer:
|
||||||
|
"The free plan allows you to self-host Dokploy on your own infrastructure with unlimited deployments and full access to all features.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Do I need to provide my own server for the managed plan?",
|
||||||
|
answer:
|
||||||
|
"Yes, in the managed plan, you provide your own server, and we manage the Dokploy UI infrastructure for you.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "What happens if I need more than one server?",
|
||||||
|
answer:
|
||||||
|
"Each additional server costs $3.99/month and can be easily added to your account.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
question: "Can I use my custom domain with Dokploy?",
|
||||||
|
answer:
|
||||||
|
"Yes, custom domain support is available on all plans, including the free version.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Is there a limit on the number of deployments?",
|
||||||
|
answer:
|
||||||
|
"No, there is no limit on the number of deployments in any of the plans.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Do I have to manually configure Traefik?",
|
||||||
|
answer:
|
||||||
|
"Dokploy offers dynamic Traefik configuration out-of-the-box, so no manual setup is needed.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
question: "How do automated backups work?",
|
||||||
|
answer:
|
||||||
|
"Automated backups are included in the managed plan and are limited to database backups only.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "What kind of support do you offer?",
|
||||||
|
answer:
|
||||||
|
"We offer community support for the free plan and priority support for paid plans.",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Is Dokploy open-source?",
|
||||||
|
answer:
|
||||||
|
"Yes, Dokploy is fully open-source. You can contribute or modify it as needed for your projects.",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
export function Faqs() {
|
||||||
|
return (
|
||||||
|
<section
|
||||||
|
id="faqs"
|
||||||
|
aria-labelledby="faq-title"
|
||||||
|
className="relative overflow-hidden bg-black py-20 sm:py-32"
|
||||||
|
>
|
||||||
|
<Container className="relative">
|
||||||
|
<div className="mx-auto max-w-2xl lg:mx-0">
|
||||||
|
<h2
|
||||||
|
id="faq-title"
|
||||||
|
className="font-display text-3xl tracking-tight text-primary sm:text-4xl"
|
||||||
|
>
|
||||||
|
{"Frequently asked questions"}
|
||||||
|
</h2>
|
||||||
|
<p className="mt-4 text-lg tracking-tight text-muted-foreground">
|
||||||
|
If you can’t find what you’re looking for, please submit an issue
|
||||||
|
to:{" "}
|
||||||
|
<Link href={"mailto:support@dokploy.com"} className="text-primary">
|
||||||
|
support@dokploy.com
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<ul className="mx-auto mt-16 grid max-w-2xl grid-cols-1 gap-8 lg:max-w-none lg:grid-cols-3">
|
||||||
|
{faqs.map((column, columnIndex) => (
|
||||||
|
<li key={columnIndex}>
|
||||||
|
<ul className="flex flex-col gap-y-8">
|
||||||
|
{column.map((faq, faqIndex) => (
|
||||||
|
<li key={faqIndex}>
|
||||||
|
<h3 className="font-display text-lg leading-7 text-primary">
|
||||||
|
{faq.question}
|
||||||
|
</h3>
|
||||||
|
<p className="mt-4 text-sm text-muted-foreground">
|
||||||
|
{faq.answer}
|
||||||
|
</p>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
))}
|
||||||
|
</ul>
|
||||||
|
</Container>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
||||||
29
apps/website/components/ui/switch.tsx
Normal file
29
apps/website/components/ui/switch.tsx
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
|
import * as SwitchPrimitives from "@radix-ui/react-switch";
|
||||||
|
import * as React from "react";
|
||||||
|
|
||||||
|
import { cn } from "@/lib/utils";
|
||||||
|
|
||||||
|
const Switch = React.forwardRef<
|
||||||
|
React.ElementRef<typeof SwitchPrimitives.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof SwitchPrimitives.Root>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<SwitchPrimitives.Root
|
||||||
|
className={cn(
|
||||||
|
"peer inline-flex h-6 w-11 shrink-0 cursor-pointer items-center rounded-full border-2 border-transparent transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background disabled:cursor-not-allowed disabled:opacity-50 data-[state=checked]:bg-primary data-[state=unchecked]:bg-input",
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
ref={ref}
|
||||||
|
>
|
||||||
|
<SwitchPrimitives.Thumb
|
||||||
|
className={cn(
|
||||||
|
"pointer-events-none block h-5 w-5 rounded-full bg-background shadow-lg ring-0 transition-transform data-[state=checked]:translate-x-5 data-[state=unchecked]:translate-x-0",
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
</SwitchPrimitives.Root>
|
||||||
|
));
|
||||||
|
Switch.displayName = SwitchPrimitives.Root.displayName;
|
||||||
|
|
||||||
|
export { Switch };
|
||||||
@@ -56,23 +56,23 @@
|
|||||||
},
|
},
|
||||||
"faq": {
|
"faq": {
|
||||||
"title": "Frequently asked questions",
|
"title": "Frequently asked questions",
|
||||||
"des": "If you can’t find what you’re looking for, please submit an issue through our GitHub repository or ask questions on our Discord.",
|
"des": "If you can't find what you're looking for, please submit an issue through our GitHub repository or ask questions on our Discord.",
|
||||||
"q1": "What is dokploy?",
|
"q1": "What is dokploy?",
|
||||||
"a1": "Dokploy is a stable, easy-to-use deployment solution designed to simplify the application management process. Think of Dokploy as a free alternative self-hostable solution to platforms like Heroku, Vercel, and Netlify.",
|
"a1": "Dokploy is a stable, easy-to-use deployment solution designed to simplify the application management process. Think of Dokploy as a free alternative self-hostable solution to platforms like Heroku, Vercel, and Netlify.",
|
||||||
"q2": "Why Choose Dokploy?",
|
"q2": "Why Choose Dokploy?",
|
||||||
"a2": "Simplicity, Flexibility, and Fast",
|
"a2": "Dokploy offers simplicity, flexibility, and speed in application deployment and management.",
|
||||||
"q3": "Is free?",
|
"q3": "Is Dokploy free?",
|
||||||
"a3": "Yes, dokploy is totally free. You can use it for personal projects, small teams, or even for large-scale applications.",
|
"a3": "Yes, Dokploy is totally free. You can use it for personal projects, small teams, or even for large-scale applications.",
|
||||||
"q4": "Is it open source?",
|
"q4": "Is it open source?",
|
||||||
"a4": "Yes, dokploy is open source and free to use.",
|
"a4": "Yes, Dokploy is open source and free to use.",
|
||||||
"q5": "What types of languages can i deploy with dokploy?",
|
"q5": "What types of languages can I deploy with Dokploy?",
|
||||||
"a5": "Dokploy do not restrict programming languages. you are free to choose your preferred language and framework.",
|
"a5": "Dokploy does not restrict programming languages. You are free to choose your preferred language and framework.",
|
||||||
"q6": "How do I request a feature or report a bug?",
|
"q6": "How do I request a feature or report a bug?",
|
||||||
"a6": "Currently we are working on fixing bug fixes, but we will be releasing new features soon. You can also request features or report bugs.",
|
"a6": "To request a feature or report a bug, please create an issue on our GitHub repository or ask in our Discord channel. We are currently focused on addressing existing bugs and plan to release new features soon.",
|
||||||
"q7": "Do you track the usage of Dokploy?",
|
"q7": "Do you track the usage of Dokploy?",
|
||||||
"a7": "No, we don't track any usage data.",
|
"a7": "No, we don't track any usage data.",
|
||||||
"q8": "Are there any user forums or communities where I can interact with other users?",
|
"q8": "Are there any user forums or communities where I can interact with other users?",
|
||||||
"a8": "Yes, we have active github discussions where you can share ideas, ask for help, and connect with other users.",
|
"a8": "Yes, we have active GitHub discussions where you can share ideas, ask for help, and connect with other users.",
|
||||||
"q9": "What types of applications can I deploy with Dokploy?",
|
"q9": "What types of applications can I deploy with Dokploy?",
|
||||||
"a9": "Dokploy supports a variety of applications, including those built with Docker, as well as applications from any Git repository, offering custom builds with Nixpacks, Dockerfiles, or Buildpacks like Heroku and Paketo.",
|
"a9": "Dokploy supports a variety of applications, including those built with Docker, as well as applications from any Git repository, offering custom builds with Nixpacks, Dockerfiles, or Buildpacks like Heroku and Paketo.",
|
||||||
"q10": "How does Dokploy handle database management?",
|
"q10": "How does Dokploy handle database management?",
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
"@headlessui/tailwindcss": "^0.2.0",
|
"@headlessui/tailwindcss": "^0.2.0",
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@radix-ui/react-switch": "^1.0.3",
|
||||||
"@types/node": "20.4.6",
|
"@types/node": "20.4.6",
|
||||||
"autoprefixer": "^10.4.12",
|
"autoprefixer": "^10.4.12",
|
||||||
"class-variance-authority": "^0.7.0",
|
"class-variance-authority": "^0.7.0",
|
||||||
|
|||||||
@@ -61,6 +61,11 @@ install_dokploy() {
|
|||||||
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error: Failed to initialize Docker Swarm" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Swarm initialized"
|
echo "Swarm initialized"
|
||||||
|
|
||||||
docker network rm -f dokploy-network 2>/dev/null
|
docker network rm -f dokploy-network 2>/dev/null
|
||||||
|
|||||||
@@ -45,6 +45,11 @@ install_dokploy() {
|
|||||||
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error: Failed to initialize Docker Swarm" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Swarm initialized"
|
echo "Swarm initialized"
|
||||||
|
|
||||||
docker network rm -f dokploy-network 2>/dev/null
|
docker network rm -f dokploy-network 2>/dev/null
|
||||||
|
|||||||
@@ -59,6 +59,11 @@ install_dokploy() {
|
|||||||
advertise_addr=$(get_ip)
|
advertise_addr=$(get_ip)
|
||||||
|
|
||||||
docker swarm init --advertise-addr $advertise_addr
|
docker swarm init --advertise-addr $advertise_addr
|
||||||
|
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo "Error: Failed to initialize Docker Swarm" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo "Swarm initialized"
|
echo "Swarm initialized"
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@
|
|||||||
"format-and-lint": "biome check .",
|
"format-and-lint": "biome check .",
|
||||||
"check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true",
|
"check": "biome check --write --no-errors-on-unmatched --files-ignore-unknown=true",
|
||||||
"format-and-lint:fix": "biome check . --write",
|
"format-and-lint:fix": "biome check . --write",
|
||||||
"prepare": "node ./.config/.husky/install.mjs"
|
"prepare": "node .husky/install.mjs"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"dotenv": "16.4.5",
|
"dotenv": "16.4.5",
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
"tsx": "4.16.2",
|
"tsx": "4.16.2",
|
||||||
"lint-staged": "^15.2.7",
|
"lint-staged": "^15.2.7",
|
||||||
"@biomejs/biome": "1.8.3",
|
"@biomejs/biome": "1.8.3",
|
||||||
"husky": "^9.0.11",
|
"husky": "^9.1.6",
|
||||||
"@commitlint/cli": "^19.3.0",
|
"@commitlint/cli": "^19.3.0",
|
||||||
"@commitlint/config-conventional": "^19.2.2",
|
"@commitlint/config-conventional": "^19.2.2",
|
||||||
"@types/node": "^18.17.0"
|
"@types/node": "^18.17.0"
|
||||||
|
|||||||
@@ -1,23 +1,23 @@
|
|||||||
// import { sql } from "drizzle-orm";
|
import { sql } from "drizzle-orm";
|
||||||
// // Credits to Louistiti from Drizzle Discord: https://discord.com/channels/1043890932593987624/1130802621750448160/1143083373535973406
|
// Credits to Louistiti from Drizzle Discord: https://discord.com/channels/1043890932593987624/1130802621750448160/1143083373535973406
|
||||||
// import { drizzle } from "drizzle-orm/postgres-js";
|
import { drizzle } from "drizzle-orm/postgres-js";
|
||||||
// import postgres from "postgres";
|
import postgres from "postgres";
|
||||||
|
|
||||||
// const connectionString = process.env.DATABASE_URL || "";
|
const connectionString = process.env.DATABASE_URL || "";
|
||||||
|
|
||||||
// const pg = postgres(connectionString, { max: 1 });
|
const pg = postgres(connectionString, { max: 1 });
|
||||||
// const db = drizzle(pg);
|
const db = drizzle(pg);
|
||||||
|
|
||||||
// const clearDb = async (): Promise<void> => {
|
const clearDb = async (): Promise<void> => {
|
||||||
// try {
|
try {
|
||||||
// const tablesQuery = sql<string>`DROP SCHEMA public CASCADE; CREATE SCHEMA public; DROP schema drizzle CASCADE;`;
|
const tablesQuery = sql<string>`DROP SCHEMA public CASCADE; CREATE SCHEMA public; DROP schema drizzle CASCADE;`;
|
||||||
// const tables = await db.execute(tablesQuery);
|
const tables = await db.execute(tablesQuery);
|
||||||
// console.log(tables);
|
console.log(tables);
|
||||||
// await pg.end();
|
await pg.end();
|
||||||
// } catch (error) {
|
} catch (error) {
|
||||||
// console.error("Error to clean database", error);
|
console.error("Error to clean database", error);
|
||||||
// } finally {
|
} finally {
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
|
|
||||||
// clearDb();
|
clearDb();
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { IS_CLOUD, paths } from "@/server/constants";
|
import { IS_CLOUD, paths } from "@/server/constants";
|
||||||
import { findAdmin, updateAdmin } from "@/server/services/admin";
|
import { updateAdmin } from "@/server/services/admin";
|
||||||
import { type RotatingFileStream, createStream } from "rotating-file-stream";
|
import { type RotatingFileStream, createStream } from "rotating-file-stream";
|
||||||
|
import { db } from "../../db";
|
||||||
import { execAsync } from "../process/execAsync";
|
import { execAsync } from "../process/execAsync";
|
||||||
|
|
||||||
class LogRotationManager {
|
class LogRotationManager {
|
||||||
@@ -29,13 +30,17 @@ class LogRotationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private async getStateFromDB(): Promise<boolean> {
|
private async getStateFromDB(): Promise<boolean> {
|
||||||
const setting = await findAdmin();
|
const setting = await db.query.admins.findFirst({});
|
||||||
return setting?.enableLogRotation ?? false;
|
return setting?.enableLogRotation ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async setStateInDB(active: boolean): Promise<void> {
|
private async setStateInDB(active: boolean): Promise<void> {
|
||||||
const admin = await findAdmin();
|
const admin = await db.query.admins.findFirst({});
|
||||||
await updateAdmin(admin.authId, {
|
|
||||||
|
if (!admin) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await updateAdmin(admin?.authId, {
|
||||||
enableLogRotation: active,
|
enableLogRotation: active,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
|
|||||||
mongo.serverId,
|
mongo.serverId,
|
||||||
appName,
|
appName,
|
||||||
);
|
);
|
||||||
const mongoDumpCommand = `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --authenticationDatabase=admin --gzip"`;
|
const mongoDumpCommand = `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --archive --authenticationDatabase=admin --gzip"`;
|
||||||
|
|
||||||
await execAsyncRemote(
|
await execAsyncRemote(
|
||||||
mongo.serverId,
|
mongo.serverId,
|
||||||
@@ -37,7 +37,7 @@ export const runMongoBackup = async (mongo: Mongo, backup: BackupSchedule) => {
|
|||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
const { Id: containerId } = await getServiceContainer(appName);
|
const { Id: containerId } = await getServiceContainer(appName);
|
||||||
const mongoDumpCommand = `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --authenticationDatabase=admin --gzip"`;
|
const mongoDumpCommand = `docker exec ${containerId} sh -c "mongodump -d '${database}' -u '${databaseUser}' -p '${databasePassword}' --archive --authenticationDatabase=admin --gzip"`;
|
||||||
await execAsync(`${mongoDumpCommand} | ${rcloneCommand}`);
|
await execAsync(`${mongoDumpCommand} | ${rcloneCommand}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ export const cloneBitbucketRepository = async (
|
|||||||
bitbucketBranch!,
|
bitbucketBranch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
@@ -111,6 +112,7 @@ export const cloneRawBitbucketRepository = async (entity: Compose) => {
|
|||||||
bitbucketBranch!,
|
bitbucketBranch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
@@ -153,7 +155,7 @@ export const cloneRawBitbucketRepositoryRemote = async (compose: Compose) => {
|
|||||||
try {
|
try {
|
||||||
const command = `
|
const command = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
git clone --branch ${bitbucketBranch} --depth 1 ${cloneUrl} ${outputPath}
|
git clone --branch ${bitbucketBranch} --depth 1 --recurse-submodules ${cloneUrl} ${outputPath}
|
||||||
`;
|
`;
|
||||||
await execAsyncRemote(serverId, command);
|
await execAsyncRemote(serverId, command);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -206,7 +208,7 @@ export const getBitbucketCloneCommand = async (
|
|||||||
const cloneCommand = `
|
const cloneCommand = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
mkdir -p ${outputPath};
|
mkdir -p ${outputPath};
|
||||||
if ! git clone --branch ${bitbucketBranch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
if ! git clone --branch ${bitbucketBranch} --depth 1 --recurse-submodules --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||||
echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
|
echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import { createWriteStream } from "node:fs";
|
|||||||
import path, { join } from "node:path";
|
import path, { join } from "node:path";
|
||||||
import { paths } from "@/server/constants";
|
import { paths } from "@/server/constants";
|
||||||
import type { Compose } from "@/server/services/compose";
|
import type { Compose } from "@/server/services/compose";
|
||||||
import { updateSSHKeyById } from "@/server/services/ssh-key";
|
import { findSSHKeyById, updateSSHKeyById } from "@/server/services/ssh-key";
|
||||||
import { TRPCError } from "@trpc/server";
|
import { TRPCError } from "@trpc/server";
|
||||||
import { recreateDirectory } from "../filesystem/directory";
|
import { recreateDirectory } from "../filesystem/directory";
|
||||||
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
import { execAsync, execAsyncRemote } from "../process/execAsync";
|
||||||
@@ -29,13 +29,29 @@ export const cloneGitRepository = async (
|
|||||||
}
|
}
|
||||||
|
|
||||||
const writeStream = createWriteStream(logPath, { flags: "a" });
|
const writeStream = createWriteStream(logPath, { flags: "a" });
|
||||||
const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
|
const temporalKeyPath = path.join("/tmp", "id_rsa");
|
||||||
|
|
||||||
|
if (customGitSSHKeyId) {
|
||||||
|
const sshKey = await findSSHKeyById(customGitSSHKeyId);
|
||||||
|
|
||||||
|
await execAsync(`
|
||||||
|
echo "${sshKey.privateKey}" > ${temporalKeyPath}
|
||||||
|
chmod 600 ${temporalKeyPath}
|
||||||
|
`);
|
||||||
|
}
|
||||||
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
||||||
const outputPath = join(basePath, appName, "code");
|
const outputPath = join(basePath, appName, "code");
|
||||||
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!isHttpOrHttps(customGitUrl)) {
|
if (!isHttpOrHttps(customGitUrl)) {
|
||||||
|
if (!customGitSSHKeyId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message:
|
||||||
|
"Error: you are trying to clone a ssh repository without a ssh key, please set a ssh key",
|
||||||
|
});
|
||||||
|
}
|
||||||
await addHostToKnownHosts(customGitUrl);
|
await addHostToKnownHosts(customGitUrl);
|
||||||
}
|
}
|
||||||
await recreateDirectory(outputPath);
|
await recreateDirectory(outputPath);
|
||||||
@@ -74,7 +90,7 @@ export const cloneGitRepository = async (
|
|||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
...(customGitSSHKeyId && {
|
...(customGitSSHKeyId && {
|
||||||
GIT_SSH_COMMAND: `ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}`,
|
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath} -o UserKnownHostsFile=${knownHostsPath}`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -122,7 +138,6 @@ export const getCustomGitCloneCommand = async (
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
|
|
||||||
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
const basePath = isCompose ? COMPOSE_PATH : APPLICATIONS_PATH;
|
||||||
const outputPath = join(basePath, appName, "code");
|
const outputPath = join(basePath, appName, "code");
|
||||||
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
||||||
@@ -136,6 +151,13 @@ export const getCustomGitCloneCommand = async (
|
|||||||
try {
|
try {
|
||||||
const command = [];
|
const command = [];
|
||||||
if (!isHttpOrHttps(customGitUrl)) {
|
if (!isHttpOrHttps(customGitUrl)) {
|
||||||
|
if (!customGitSSHKeyId) {
|
||||||
|
command.push(
|
||||||
|
`echo "Error: you are trying to clone a ssh repository without a ssh key, please set a ssh key ❌" >> ${logPath};
|
||||||
|
exit 1;
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
command.push(addHostToKnownHostsCommand(customGitUrl));
|
command.push(addHostToKnownHostsCommand(customGitUrl));
|
||||||
}
|
}
|
||||||
command.push(`rm -rf ${outputPath};`);
|
command.push(`rm -rf ${outputPath};`);
|
||||||
@@ -144,13 +166,19 @@ export const getCustomGitCloneCommand = async (
|
|||||||
`echo "Cloning Custom Git ${customGitUrl}" to ${outputPath}: ✅ >> ${logPath};`,
|
`echo "Cloning Custom Git ${customGitUrl}" to ${outputPath}: ✅ >> ${logPath};`,
|
||||||
);
|
);
|
||||||
if (customGitSSHKeyId) {
|
if (customGitSSHKeyId) {
|
||||||
|
const sshKey = await findSSHKeyById(customGitSSHKeyId);
|
||||||
|
const gitSshCommand = `ssh -i /tmp/id_rsa -o UserKnownHostsFile=${knownHostsPath}`;
|
||||||
command.push(
|
command.push(
|
||||||
`GIT_SSH_COMMAND="ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}"`,
|
`
|
||||||
|
echo "${sshKey.privateKey}" > /tmp/id_rsa
|
||||||
|
chmod 600 /tmp/id_rsa
|
||||||
|
export GIT_SSH_COMMAND="${gitSshCommand}"
|
||||||
|
`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
command.push(
|
command.push(
|
||||||
`if ! git clone --branch ${customGitBranch} --depth 1 --progress ${customGitUrl} ${outputPath} >> ${logPath} 2>&1; then
|
`if ! git clone --branch ${customGitBranch} --depth 1 --recurse-submodules --progress ${customGitUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||||
echo "❌ [ERROR] Fail to clone the repository ${customGitUrl}" >> ${logPath};
|
echo "❌ [ERROR] Fail to clone the repository ${customGitUrl}" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
@@ -184,7 +212,7 @@ const addHostToKnownHosts = async (repositoryURL: string) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const addHostToKnownHostsCommand = (repositoryURL: string) => {
|
const addHostToKnownHostsCommand = (repositoryURL: string) => {
|
||||||
const { SSH_PATH } = paths();
|
const { SSH_PATH } = paths(true);
|
||||||
const { domain, port } = sanitizeRepoPathSSH(repositoryURL);
|
const { domain, port } = sanitizeRepoPathSSH(repositoryURL);
|
||||||
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
||||||
|
|
||||||
@@ -242,13 +270,31 @@ export const cloneGitRawRepository = async (entity: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { SSH_PATH, COMPOSE_PATH } = paths();
|
const { SSH_PATH, COMPOSE_PATH } = paths();
|
||||||
const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
|
const temporalKeyPath = path.join("/tmp", "id_rsa");
|
||||||
const basePath = COMPOSE_PATH;
|
const basePath = COMPOSE_PATH;
|
||||||
const outputPath = join(basePath, appName, "code");
|
const outputPath = join(basePath, appName, "code");
|
||||||
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
||||||
|
|
||||||
|
if (customGitSSHKeyId) {
|
||||||
|
const sshKey = await findSSHKeyById(customGitSSHKeyId);
|
||||||
|
|
||||||
|
await execAsync(`
|
||||||
|
echo "${sshKey.privateKey}" > ${temporalKeyPath}
|
||||||
|
chmod 600 ${temporalKeyPath}
|
||||||
|
`);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await addHostToKnownHosts(customGitUrl);
|
if (!isHttpOrHttps(customGitUrl)) {
|
||||||
|
if (!customGitSSHKeyId) {
|
||||||
|
throw new TRPCError({
|
||||||
|
code: "BAD_REQUEST",
|
||||||
|
message:
|
||||||
|
"Error: you are trying to clone a ssh repository without a ssh key, please set a ssh key",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
await addHostToKnownHosts(customGitUrl);
|
||||||
|
}
|
||||||
await recreateDirectory(outputPath);
|
await recreateDirectory(outputPath);
|
||||||
|
|
||||||
if (customGitSSHKeyId) {
|
if (customGitSSHKeyId) {
|
||||||
@@ -266,6 +312,7 @@ export const cloneGitRawRepository = async (entity: {
|
|||||||
customGitBranch,
|
customGitBranch,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
|
"--recurse-submodules",
|
||||||
customGitUrl,
|
customGitUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
@@ -275,7 +322,7 @@ export const cloneGitRawRepository = async (entity: {
|
|||||||
env: {
|
env: {
|
||||||
...process.env,
|
...process.env,
|
||||||
...(customGitSSHKeyId && {
|
...(customGitSSHKeyId && {
|
||||||
GIT_SSH_COMMAND: `ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}`,
|
GIT_SSH_COMMAND: `ssh -i ${temporalKeyPath} -o UserKnownHostsFile=${knownHostsPath}`,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -308,7 +355,6 @@ export const cloneRawGitRepositoryRemote = async (compose: Compose) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const { SSH_PATH, COMPOSE_PATH } = paths(true);
|
const { SSH_PATH, COMPOSE_PATH } = paths(true);
|
||||||
const keyPath = path.join(SSH_PATH, `${customGitSSHKeyId}_rsa`);
|
|
||||||
const basePath = COMPOSE_PATH;
|
const basePath = COMPOSE_PATH;
|
||||||
const outputPath = join(basePath, appName, "code");
|
const outputPath = join(basePath, appName, "code");
|
||||||
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
const knownHostsPath = path.join(SSH_PATH, "known_hosts");
|
||||||
@@ -322,18 +368,31 @@ export const cloneRawGitRepositoryRemote = async (compose: Compose) => {
|
|||||||
try {
|
try {
|
||||||
const command = [];
|
const command = [];
|
||||||
if (!isHttpOrHttps(customGitUrl)) {
|
if (!isHttpOrHttps(customGitUrl)) {
|
||||||
|
if (!customGitSSHKeyId) {
|
||||||
|
command.push(
|
||||||
|
`echo "Error: you are trying to clone a ssh repository without a ssh key, please set a ssh key ❌" ;
|
||||||
|
exit 1;
|
||||||
|
`,
|
||||||
|
);
|
||||||
|
}
|
||||||
command.push(addHostToKnownHostsCommand(customGitUrl));
|
command.push(addHostToKnownHostsCommand(customGitUrl));
|
||||||
}
|
}
|
||||||
command.push(`rm -rf ${outputPath};`);
|
command.push(`rm -rf ${outputPath};`);
|
||||||
command.push(`mkdir -p ${outputPath};`);
|
command.push(`mkdir -p ${outputPath};`);
|
||||||
if (customGitSSHKeyId) {
|
if (customGitSSHKeyId) {
|
||||||
|
const sshKey = await findSSHKeyById(customGitSSHKeyId);
|
||||||
|
const gitSshCommand = `ssh -i /tmp/id_rsa -o UserKnownHostsFile=${knownHostsPath}`;
|
||||||
command.push(
|
command.push(
|
||||||
`GIT_SSH_COMMAND="ssh -i ${keyPath} -o UserKnownHostsFile=${knownHostsPath}"`,
|
`
|
||||||
|
echo "${sshKey.privateKey}" > /tmp/id_rsa
|
||||||
|
chmod 600 /tmp/id_rsa
|
||||||
|
export GIT_SSH_COMMAND="${gitSshCommand}"
|
||||||
|
`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
command.push(
|
command.push(
|
||||||
`if ! git clone --branch ${customGitBranch} --depth 1 --progress ${customGitUrl} ${outputPath} ; then
|
`if ! git clone --branch ${customGitBranch} --depth 1 --recurse-submodules --progress ${customGitUrl} ${outputPath} ; then
|
||||||
echo "[ERROR] Fail to clone the repository ";
|
echo "[ERROR] Fail to clone the repository ";
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
|
|||||||
@@ -125,6 +125,7 @@ export const cloneGithubRepository = async (
|
|||||||
branch!,
|
branch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
@@ -204,7 +205,7 @@ export const getGithubCloneCommand = async (
|
|||||||
const cloneCommand = `
|
const cloneCommand = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
mkdir -p ${outputPath};
|
mkdir -p ${outputPath};
|
||||||
if ! git clone --branch ${branch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
if ! git clone --branch ${branch} --depth 1 --recurse-submodules --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||||
echo "❌ [ERROR] Fallo al clonar el repositorio ${repoclone}" >> ${logPath};
|
echo "❌ [ERROR] Fallo al clonar el repositorio ${repoclone}" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
@@ -239,6 +240,7 @@ export const cloneRawGithubRepository = async (entity: Compose) => {
|
|||||||
branch!,
|
branch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
|
|||||||
@@ -137,6 +137,7 @@ export const cloneGitlabRepository = async (
|
|||||||
gitlabBranch!,
|
gitlabBranch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
@@ -225,7 +226,7 @@ export const getGitlabCloneCommand = async (
|
|||||||
const cloneCommand = `
|
const cloneCommand = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
mkdir -p ${outputPath};
|
mkdir -p ${outputPath};
|
||||||
if ! git clone --branch ${gitlabBranch} --depth 1 --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
if ! git clone --branch ${gitlabBranch} --depth 1 --recurse-submodules --progress ${cloneUrl} ${outputPath} >> ${logPath} 2>&1; then
|
||||||
echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
|
echo "❌ [ERROR] Fail to clone the repository ${repoclone}" >> ${logPath};
|
||||||
exit 1;
|
exit 1;
|
||||||
fi
|
fi
|
||||||
@@ -361,6 +362,7 @@ export const cloneRawGitlabRepository = async (entity: Compose) => {
|
|||||||
gitlabBranch!,
|
gitlabBranch!,
|
||||||
"--depth",
|
"--depth",
|
||||||
"1",
|
"1",
|
||||||
|
"--recurse-submodules",
|
||||||
cloneUrl,
|
cloneUrl,
|
||||||
outputPath,
|
outputPath,
|
||||||
"--progress",
|
"--progress",
|
||||||
@@ -395,7 +397,7 @@ export const cloneRawGitlabRepositoryRemote = async (compose: Compose) => {
|
|||||||
try {
|
try {
|
||||||
const command = `
|
const command = `
|
||||||
rm -rf ${outputPath};
|
rm -rf ${outputPath};
|
||||||
git clone --branch ${branch} --depth 1 ${cloneUrl} ${outputPath}
|
git clone --branch ${branch} --depth 1 --recurse-submodules ${cloneUrl} ${outputPath}
|
||||||
`;
|
`;
|
||||||
await execAsyncRemote(serverId, command);
|
await execAsyncRemote(serverId, command);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
13
pnpm-lock.yaml
generated
13
pnpm-lock.yaml
generated
@@ -31,8 +31,8 @@ importers:
|
|||||||
specifier: 0.20.2
|
specifier: 0.20.2
|
||||||
version: 0.20.2
|
version: 0.20.2
|
||||||
husky:
|
husky:
|
||||||
specifier: ^9.0.11
|
specifier: ^9.1.6
|
||||||
version: 9.1.3
|
version: 9.1.6
|
||||||
lint-staged:
|
lint-staged:
|
||||||
specifier: ^15.2.7
|
specifier: ^15.2.7
|
||||||
version: 15.2.7
|
version: 15.2.7
|
||||||
@@ -520,6 +520,9 @@ importers:
|
|||||||
'@radix-ui/react-slot':
|
'@radix-ui/react-slot':
|
||||||
specifier: ^1.0.2
|
specifier: ^1.0.2
|
||||||
version: 1.1.0(@types/react@18.3.5)(react@18.2.0)
|
version: 1.1.0(@types/react@18.3.5)(react@18.2.0)
|
||||||
|
'@radix-ui/react-switch':
|
||||||
|
specifier: ^1.0.3
|
||||||
|
version: 1.1.0(@types/react-dom@18.3.0)(@types/react@18.3.5)(react-dom@18.2.0(react@18.2.0))(react@18.2.0)
|
||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: 20.4.6
|
specifier: 20.4.6
|
||||||
version: 20.4.6
|
version: 20.4.6
|
||||||
@@ -5501,8 +5504,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==}
|
||||||
engines: {node: '>=16.17.0'}
|
engines: {node: '>=16.17.0'}
|
||||||
|
|
||||||
husky@9.1.3:
|
husky@9.1.6:
|
||||||
resolution: {integrity: sha512-ET3TQmQgdIu0pt+jKkpo5oGyg/4MQZpG6xcam5J5JyNJV+CBT23OBpCF15bKHKycRyMH9k6ONy8g2HdGIsSkMQ==}
|
resolution: {integrity: sha512-sqbjZKK7kf44hfdE94EoX8MZNk0n7HeW37O4YrVGCF4wzgQjp+akPAkfUK5LZ6KuR/6sqeAVuXHji+RzQgOn5A==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
@@ -13813,7 +13816,7 @@ snapshots:
|
|||||||
|
|
||||||
human-signals@5.0.0: {}
|
human-signals@5.0.0: {}
|
||||||
|
|
||||||
husky@9.1.3: {}
|
husky@9.1.6: {}
|
||||||
|
|
||||||
hyperdyperid@1.2.0: {}
|
hyperdyperid@1.2.0: {}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user