fix: rewrite Dokploy client with correct API format (tRPC + x-api-key)
- Verified endpoints from @dokploy/mcp source code:
- Auth: x-api-key header (not Authorization: Bearer)
- Base URL: {url}/api
- application.create → needs environmentId (not projectId)
- Git provider saved separately: saveGiteaProvider / saveGithubProvider / saveGitProvider
- Environment vars saved via saveEnvironment
- Deploy is a separate call after create
- Domain attach via domain.create
- Provisioning flow now multi-step:
1. application.create
2. saveGiteaProvider or saveGithubProvider (based on gitRepo URL)
3. saveEnvironment (env vars)
4. application.deploy
5. domain.create (optional, non-fatal)
- Removed database provisioning (Dokploy apps use their own DB via env vars)
- Teardown simplified: deleteDomain → deleteApplication
- Updated instances route + cleanup route to match new API
This commit is contained in:
1 parent
970986fef7
commit
617f00b715
4 files changed
+209
-195
No files matched your search
@@ -184,8 +184,7 @@ export async function DELETE(_req: Request, { params }: { params: Promise<{ id:
|
|||||||
try {
|
try {
|
||||||
const result = await dokploy.teardownInstance({
|
const result = await dokploy.teardownInstance({
|
||||||
dokployAppId: instance.dokployAppId ?? undefined,
|
dokployAppId: instance.dokployAppId ?? undefined,
|
||||||
dokployDbId: instance.dokployDbId ?? undefined,
|
dokployDomainId: meta.dokployDomainId,
|
||||||
dnsDomainId: meta.dokployDomainId,
|
|
||||||
})
|
})
|
||||||
steps.push(...result.steps)
|
steps.push(...result.steps)
|
||||||
await db.instanceEvent.create({
|
await db.instanceEvent.create({
|
||||||
|
|||||||
@@ -88,8 +88,7 @@ export async function POST(req: Request) {
|
|||||||
try {
|
try {
|
||||||
const r = await dokploy.teardownInstance({
|
const r = await dokploy.teardownInstance({
|
||||||
dokployAppId: inst.dokployAppId ?? undefined,
|
dokployAppId: inst.dokployAppId ?? undefined,
|
||||||
dokployDbId: inst.dokployDbId ?? undefined,
|
dokployDomainId: meta.dokployDomainId,
|
||||||
dnsDomainId: meta.dokployDomainId,
|
|
||||||
})
|
})
|
||||||
steps.push(...r.steps)
|
steps.push(...r.steps)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ export async function POST(req: Request) {
|
|||||||
gitRepo,
|
gitRepo,
|
||||||
gitBranch,
|
gitBranch,
|
||||||
envVars: instanceEnv,
|
envVars: instanceEnv,
|
||||||
databaseName: `${product}_${subdomain}`.replace(/-/g, '_').slice(0, 40),
|
gitProvider: gitRepo.includes('github.com') ? 'github' : 'gitea',
|
||||||
cpuLimit: plan === 'enterprise' ? '2' : plan === 'pro' ? '1' : '0.5',
|
cpuLimit: plan === 'enterprise' ? '2' : plan === 'pro' ? '1' : '0.5',
|
||||||
memoryLimit: plan === 'enterprise' ? '2048m' : plan === 'pro' ? '1024m' : '512m',
|
memoryLimit: plan === 'enterprise' ? '2048m' : plan === 'pro' ? '1024m' : '512m',
|
||||||
}
|
}
|
||||||
@@ -183,8 +183,7 @@ export async function POST(req: Request) {
|
|||||||
result: 'ok',
|
result: 'ok',
|
||||||
details: JSON.stringify({
|
details: JSON.stringify({
|
||||||
appId: result.app.applicationId,
|
appId: result.app.applicationId,
|
||||||
dbId: result.database?.databaseId,
|
domainId: result.domain?.domainId,
|
||||||
domainId: result.domain.domainId,
|
|
||||||
url: result.url,
|
url: result.url,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
@@ -227,15 +226,11 @@ export async function POST(req: Request) {
|
|||||||
data: {
|
data: {
|
||||||
status: isDemo ? 'demo_active' : 'active',
|
status: isDemo ? 'demo_active' : 'active',
|
||||||
dokployAppId: result.app.applicationId,
|
dokployAppId: result.app.applicationId,
|
||||||
dokployDbId: result.database?.databaseId ?? null,
|
|
||||||
dokployDbName: result.database?.database ?? null,
|
|
||||||
dokployDbUser: result.database?.username ?? null,
|
|
||||||
dokployDbPassword: result.database?.password ?? null,
|
|
||||||
dnsRecordId,
|
dnsRecordId,
|
||||||
metadata: JSON.stringify({
|
metadata: JSON.stringify({
|
||||||
gitRepo,
|
gitRepo,
|
||||||
gitBranch,
|
gitBranch,
|
||||||
dokployDomainId: result.domain.domainId,
|
dokployDomainId: result.domain?.domainId ?? null,
|
||||||
url: result.url,
|
url: result.url,
|
||||||
provisionedAt: now.toISOString(),
|
provisionedAt: now.toISOString(),
|
||||||
}),
|
}),
|
||||||
|
|||||||
+204
-183
@@ -1,102 +1,90 @@
|
|||||||
/**
|
/**
|
||||||
* Dokploy API Client
|
* Dokploy API Client (v2 — correct tRPC-based API)
|
||||||
* -------------------
|
* -----------------------------------------------
|
||||||
* Manages tenant instances on the Dokploy server: create applications from
|
* Manages tenant instances on the Dokploy server.
|
||||||
* a Git repo, deploy, attach domains, create databases, suspend/delete.
|
*
|
||||||
|
* API format (verified from @dokploy/mcp source):
|
||||||
|
* - Base URL: {DOKPLOY_URL}/api
|
||||||
|
* - Auth: x-api-key header (NOT Authorization: Bearer)
|
||||||
|
* - Endpoints: /application.create, /application.deploy, /application.delete, etc.
|
||||||
|
* - Application creation is multi-step:
|
||||||
|
* 1. POST /application.create { name, environmentId, sourceType: 'gitea' }
|
||||||
|
* 2. POST /application.saveGiteaProvider { applicationId, giteaOwner, giteaRepository, giteaBranch, giteaBuildPath }
|
||||||
|
* (or saveGitProvider for generic git, saveGithubProvider for github)
|
||||||
|
* 3. POST /application.saveEnvironment { applicationId, env: [...] }
|
||||||
|
* 4. POST /application.deploy { applicationId }
|
||||||
*
|
*
|
||||||
* Config (env):
|
* Config (env):
|
||||||
* DOKPLOY_URL - e.g. https://cp.project-url.eu
|
* DOKPLOY_URL - e.g. https://cp.project-url.eu
|
||||||
* DOKPLOY_API_KEY - bearer token
|
* DOKPLOY_API_KEY - x-api-key value
|
||||||
* DOKPLOY_SERVER_ID - the Dokploy server ID to deploy to (default "default")
|
* DOKPLOY_ENVIRONMENT_ID - the Dokploy environment ID to create apps under
|
||||||
* DOKPLOY_PROJECT_ID - the Dokploy project to put tenant apps under
|
* DOKPLOY_SERVER_ID - (optional) specific server, null = default
|
||||||
*
|
|
||||||
* NOTE: Dokploy API endpoints should be verified against the current API docs
|
|
||||||
* at implementation time. The endpoints below are based on the Dokploy REST API.
|
|
||||||
* See: https://docs.dokploy.com/docs/api
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
export interface DokployConfig {
|
export interface DokployConfig {
|
||||||
url: string
|
url: string
|
||||||
apiKey: string
|
apiKey: string
|
||||||
serverId?: string
|
environmentId?: string
|
||||||
projectId?: string
|
serverId?: string | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DokployApp {
|
export interface DokployApp {
|
||||||
applicationId: string
|
applicationId: string
|
||||||
name: string
|
name: string
|
||||||
|
appName?: string
|
||||||
status?: string
|
status?: string
|
||||||
url?: string
|
previewDomain?: string
|
||||||
createdAt?: string
|
createdAt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DokployDatabase {
|
|
||||||
databaseId: string
|
|
||||||
name: string
|
|
||||||
type: string // postgres|mysql|redis|...
|
|
||||||
host: string
|
|
||||||
port: number
|
|
||||||
database: string
|
|
||||||
username: string
|
|
||||||
password: string
|
|
||||||
externalUrl?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DokployDomain {
|
export interface DokployDomain {
|
||||||
domainId: string
|
domainId: string
|
||||||
hostName: string
|
hostName: string
|
||||||
applicationId: string
|
applicationId: string
|
||||||
https: boolean
|
https: boolean
|
||||||
certificateStatus?: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProvisionInstanceInput {
|
export interface ProvisionInstanceInput {
|
||||||
name: string
|
name: string
|
||||||
subdomain: string
|
subdomain: string
|
||||||
gitRepo: string // e.g. https://github.com/cetinmustafa83/restov1
|
gitRepo: string
|
||||||
gitBranch: string // e.g. main
|
gitBranch: string
|
||||||
buildPath?: string // e.g. / (repo root, default)
|
buildPath?: string
|
||||||
envVars: Record<string, string>
|
envVars: Record<string, string>
|
||||||
// Resource limits (Dokploy applies these as container constraints)
|
gitProvider?: 'gitea' | 'github' | 'git' // default: gitea
|
||||||
cpuLimit?: string // e.g. "0.5" = 50% of one core
|
// Resource limits (applied via application.update after creation)
|
||||||
memoryLimit?: string // e.g. "512m"
|
cpuLimit?: string
|
||||||
// Database
|
memoryLimit?: string
|
||||||
databaseName?: string // if set, a Postgres DB is created for this instance
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProvisionInstanceResult {
|
export interface ProvisionInstanceResult {
|
||||||
app: DokployApp
|
app: DokployApp
|
||||||
database?: DokployDatabase
|
domain?: DokployDomain
|
||||||
domain: DokployDomain
|
|
||||||
url: string
|
url: string
|
||||||
}
|
}
|
||||||
|
|
||||||
class DokployError extends Error {
|
class DokployError extends Error {
|
||||||
constructor(
|
constructor(message: string, public status: number, public endpoint: string, public body?: unknown) {
|
||||||
message: string,
|
|
||||||
public status: number,
|
|
||||||
public endpoint: string,
|
|
||||||
public body?: unknown
|
|
||||||
) {
|
|
||||||
super(message)
|
super(message)
|
||||||
this.name = 'DokployError'
|
this.name = 'DokployError'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createDokployClient(config: DokployConfig) {
|
export function createDokployClient(config: DokployConfig) {
|
||||||
const baseUrl = config.url.replace(/\/$/, '')
|
const baseUrl = config.url.replace(/\/+$/, '') + '/api'
|
||||||
const serverId = config.serverId ?? 'default'
|
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
Authorization: `Bearer ${config.apiKey}`,
|
Accept: 'application/json',
|
||||||
|
'x-api-key': config.apiKey,
|
||||||
}
|
}
|
||||||
|
|
||||||
async function call<T = unknown>(endpoint: string, body?: Record<string, unknown>, method = 'POST'): Promise<T> {
|
async function call<T = unknown>(path: string, body?: Record<string, unknown>, method = 'POST'): Promise<T> {
|
||||||
const url = `${baseUrl}${endpoint}`
|
const url = `${baseUrl}${path}`
|
||||||
|
const isGet = method === 'GET'
|
||||||
const res = await fetch(url, {
|
const res = await fetch(url, {
|
||||||
method,
|
method,
|
||||||
headers,
|
headers,
|
||||||
body: body ? JSON.stringify(body) : undefined,
|
body: !isGet && body ? JSON.stringify(body) : undefined,
|
||||||
})
|
})
|
||||||
const text = await res.text()
|
const text = await res.text()
|
||||||
let json: unknown
|
let json: unknown
|
||||||
@@ -107,9 +95,9 @@ export function createDokployClient(config: DokployConfig) {
|
|||||||
}
|
}
|
||||||
if (!res.ok) {
|
if (!res.ok) {
|
||||||
throw new DokployError(
|
throw new DokployError(
|
||||||
`Dokploy API ${endpoint} failed: HTTP ${res.status}`,
|
`Dokploy API ${path} failed: HTTP ${res.status}`,
|
||||||
res.status,
|
res.status,
|
||||||
endpoint,
|
path,
|
||||||
json
|
json
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -120,111 +108,130 @@ export function createDokployClient(config: DokployConfig) {
|
|||||||
|
|
||||||
async function createApplication(input: {
|
async function createApplication(input: {
|
||||||
name: string
|
name: string
|
||||||
|
environmentId: string
|
||||||
|
sourceType?: string
|
||||||
|
serverId?: string | null
|
||||||
description?: string
|
description?: string
|
||||||
gitRepo: string
|
|
||||||
gitBranch: string
|
|
||||||
buildPath?: string
|
|
||||||
envVars?: Record<string, string>
|
|
||||||
cpuLimit?: string
|
|
||||||
memoryLimit?: string
|
|
||||||
}): Promise<DokployApp> {
|
}): Promise<DokployApp> {
|
||||||
const body: Record<string, unknown> = {
|
const body: Record<string, unknown> = {
|
||||||
name: input.name,
|
name: input.name,
|
||||||
description: input.description ?? `Tenant instance: ${input.name}`,
|
environmentId: input.environmentId,
|
||||||
serverId,
|
sourceType: input.sourceType ?? 'gitea',
|
||||||
projectName: config.projectId ?? 'default',
|
|
||||||
sourceType: 'git',
|
|
||||||
gitRepository: input.gitRepo,
|
|
||||||
gitBranch: input.gitBranch,
|
|
||||||
buildPath: input.buildPath ?? '/',
|
|
||||||
// Dokploy expects env vars as a JSON string
|
|
||||||
env: JSON.stringify(input.envVars ?? {}),
|
|
||||||
cpuLimit: input.cpuLimit ?? '0.5',
|
|
||||||
memoryLimit: input.memoryLimit ?? '512m',
|
|
||||||
}
|
}
|
||||||
const res = await call<{ applicationId?: string; app?: { applicationId: string }; [k: string]: unknown }>('/api/application.create', body)
|
if (input.serverId) body.serverId = input.serverId
|
||||||
const applicationId = res.applicationId ?? (res.app as { applicationId: string } | undefined)?.applicationId
|
if (input.description) body.description = input.description
|
||||||
|
|
||||||
|
const res = await call<{ applicationId?: string; app?: DokployApp; [k: string]: unknown }>(
|
||||||
|
'/application.create',
|
||||||
|
body
|
||||||
|
)
|
||||||
|
const applicationId = res.applicationId ?? (res.app as DokployApp | undefined)?.applicationId
|
||||||
if (!applicationId) {
|
if (!applicationId) {
|
||||||
throw new DokployError('Dokploy createApplication returned no applicationId', 200, '/api/application.create', res)
|
throw new DokployError('createApplication returned no applicationId', 200, '/application.create', res)
|
||||||
}
|
}
|
||||||
return { applicationId, name: input.name, status: 'created' }
|
return { applicationId, name: input.name, status: 'created' }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deployApplication(applicationId: string): Promise<{ deployId: string; status: string }> {
|
async function saveGiteaProvider(input: {
|
||||||
const res = await call<{ deployId?: string; jobId?: string; status?: string }>('/api/application.deploy', { applicationId })
|
applicationId: string
|
||||||
return { deployId: res.deployId ?? res.jobId ?? '', status: res.status ?? 'deploying' }
|
giteaOwner: string
|
||||||
|
giteaRepository: string
|
||||||
|
giteaBranch: string
|
||||||
|
giteaBuildPath?: string | null
|
||||||
|
giteaId?: string | null
|
||||||
|
}): Promise<void> {
|
||||||
|
await call('/application.saveGiteaProvider', {
|
||||||
|
applicationId: input.applicationId,
|
||||||
|
giteaOwner: input.giteaOwner,
|
||||||
|
giteaRepository: input.giteaRepository,
|
||||||
|
giteaBranch: input.giteaBranch,
|
||||||
|
giteaBuildPath: input.giteaBuildPath ?? '/',
|
||||||
|
giteaId: input.giteaId ?? null,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getApplication(applicationId: string): Promise<DokployApp & { status?: string; url?: string }> {
|
async function saveGitProvider(input: {
|
||||||
|
applicationId: string
|
||||||
|
gitProviderOwner?: string | null
|
||||||
|
gitProviderRepository?: string | null
|
||||||
|
gitProviderBranch?: string
|
||||||
|
gitProviderBuildPath?: string | null
|
||||||
|
gitProviderId?: string | null
|
||||||
|
}): Promise<void> {
|
||||||
|
await call('/application.saveGitProvider', {
|
||||||
|
applicationId: input.applicationId,
|
||||||
|
gitProviderOwner: input.gitProviderOwner ?? null,
|
||||||
|
gitProviderRepository: input.gitProviderRepository ?? null,
|
||||||
|
gitProviderBranch: input.gitProviderBranch ?? 'main',
|
||||||
|
gitProviderBuildPath: input.gitProviderBuildPath ?? '/',
|
||||||
|
gitProviderId: input.gitProviderId ?? null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveGithubProvider(input: {
|
||||||
|
applicationId: string
|
||||||
|
githubOwner: string
|
||||||
|
githubRepository: string
|
||||||
|
githubBranch: string
|
||||||
|
githubBuildPath?: string | null
|
||||||
|
githubId?: string | null
|
||||||
|
}): Promise<void> {
|
||||||
|
await call('/application.saveGithubProvider', {
|
||||||
|
applicationId: input.applicationId,
|
||||||
|
githubOwner: input.githubOwner,
|
||||||
|
githubRepository: input.githubRepository,
|
||||||
|
githubBranch: input.githubBranch,
|
||||||
|
githubBuildPath: input.githubBuildPath ?? '/',
|
||||||
|
githubId: input.githubId ?? null,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveEnvironment(applicationId: string, envVars: Record<string, string>): Promise<void> {
|
||||||
|
// Dokploy expects env as array of { key, value } objects OR a JSON string
|
||||||
|
const envArray = Object.entries(envVars).map(([key, value]) => ({ key, value }))
|
||||||
|
await call('/application.saveEnvironment', {
|
||||||
|
applicationId,
|
||||||
|
env: JSON.stringify(envArray),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function deployApplication(applicationId: string): Promise<{ jobId?: string; status: string }> {
|
||||||
|
const res = await call<{ jobId?: string; deployId?: string; status?: string }>('/application.deploy', {
|
||||||
|
applicationId,
|
||||||
|
})
|
||||||
|
return { jobId: res.jobId ?? res.deployId, status: res.status ?? 'deploying' }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function getApplication(applicationId: string): Promise<DokployApp> {
|
||||||
const res = await call<DokployApp & { applicationStatus?: string; previewDomain?: string }>(
|
const res = await call<DokployApp & { applicationStatus?: string; previewDomain?: string }>(
|
||||||
`/api/application.one`,
|
'/application.one',
|
||||||
{ applicationId },
|
{ applicationId },
|
||||||
|
'GET'
|
||||||
)
|
)
|
||||||
return {
|
return {
|
||||||
applicationId: res.applicationId,
|
applicationId: res.applicationId,
|
||||||
name: res.name,
|
name: res.name,
|
||||||
|
appName: res.appName,
|
||||||
status: res.applicationStatus ?? res.status,
|
status: res.applicationStatus ?? res.status,
|
||||||
url: res.previewDomain ?? res.url,
|
previewDomain: res.previewDomain,
|
||||||
createdAt: res.createdAt,
|
createdAt: res.createdAt,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteApplication(applicationId: string): Promise<void> {
|
async function deleteApplication(applicationId: string): Promise<void> {
|
||||||
await call('/api/application.delete', { applicationId, deleteVolumes: true })
|
await call('/application.delete', { applicationId })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function stopApplication(applicationId: string): Promise<void> {
|
async function stopApplication(applicationId: string): Promise<void> {
|
||||||
await call('/api/application.stop', { applicationId })
|
await call('/application.stop', { applicationId })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startApplication(applicationId: string): Promise<void> {
|
async function startApplication(applicationId: string): Promise<void> {
|
||||||
await call('/api/application.start', { applicationId })
|
await call('/application.start', { applicationId })
|
||||||
}
|
}
|
||||||
|
|
||||||
async function updateEnv(applicationId: string, envVars: Record<string, string>): Promise<void> {
|
async function redeployApplication(applicationId: string): Promise<void> {
|
||||||
await call('/api/application.update', {
|
await call('/application.redeploy', { applicationId })
|
||||||
applicationId,
|
|
||||||
env: JSON.stringify(envVars),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// --- Databases (Postgres) ---
|
|
||||||
|
|
||||||
async function createPostgresDatabase(input: {
|
|
||||||
name: string
|
|
||||||
appName: string // the application this DB belongs to
|
|
||||||
description?: string
|
|
||||||
externalUrl?: boolean
|
|
||||||
}): Promise<DokployDatabase> {
|
|
||||||
const body: Record<string, unknown> = {
|
|
||||||
name: input.name,
|
|
||||||
appName: input.appName,
|
|
||||||
description: input.description ?? `DB for ${input.appName}`,
|
|
||||||
serverId,
|
|
||||||
projectName: config.projectId ?? 'default',
|
|
||||||
externalUrl: input.externalUrl ?? false,
|
|
||||||
}
|
|
||||||
const res = await call<Record<string, unknown>>('/api/database.postgres.create', body)
|
|
||||||
// The response shape varies between Dokploy versions; handle both.
|
|
||||||
const db = (res.database as Record<string, unknown> | undefined) ?? res
|
|
||||||
const databaseId = (db.databaseId as string | undefined) ?? (res.databaseId as string | undefined)
|
|
||||||
if (!databaseId) {
|
|
||||||
throw new DokployError('Dokploy createDatabase returned no databaseId', 200, '/api/database.postgres.create', res)
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
databaseId,
|
|
||||||
name: (db.name as string | undefined) ?? input.name,
|
|
||||||
type: 'postgres',
|
|
||||||
host: (db.host as string | undefined) ?? 'postgres',
|
|
||||||
port: (db.port as number | undefined) ?? 5432,
|
|
||||||
database: (db.database as string | undefined) ?? input.name,
|
|
||||||
username: (db.username as string | undefined) ?? 'postgres',
|
|
||||||
password: (db.password as string | undefined) ?? '',
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async function deleteDatabase(databaseId: string): Promise<void> {
|
|
||||||
await call('/api/database.postgres.delete', { databaseId })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- Domains ---
|
// --- Domains ---
|
||||||
@@ -235,20 +242,17 @@ export function createDokployClient(config: DokployConfig) {
|
|||||||
path?: string
|
path?: string
|
||||||
port?: number
|
port?: number
|
||||||
https?: boolean
|
https?: boolean
|
||||||
certificateType?: 'letsencrypt' | 'none'
|
|
||||||
}): Promise<DokployDomain> {
|
}): Promise<DokployDomain> {
|
||||||
const body: Record<string, unknown> = {
|
const res = await call<{ domainId?: string; id?: string }>('/domain.create', {
|
||||||
applicationId: input.applicationId,
|
applicationId: input.applicationId,
|
||||||
hostName: input.hostName,
|
hostName: input.hostName,
|
||||||
path: input.path ?? '/',
|
path: input.path ?? '/',
|
||||||
port: input.port ?? 3000,
|
port: input.port ?? 3000,
|
||||||
https: input.https ?? true,
|
https: input.https ?? true,
|
||||||
certificateType: input.certificateType ?? 'letsencrypt',
|
})
|
||||||
}
|
|
||||||
const res = await call<{ domainId?: string; id?: string; hostName?: string; https?: boolean }>('/api/domain.create', body)
|
|
||||||
const domainId = res.domainId ?? res.id
|
const domainId = res.domainId ?? res.id
|
||||||
if (!domainId) {
|
if (!domainId) {
|
||||||
throw new DokployError('Dokploy createDomain returned no domainId', 200, '/api/domain.create', res)
|
throw new DokployError('createDomain returned no domainId', 200, '/domain.create', res)
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
domainId,
|
domainId,
|
||||||
@@ -259,55 +263,84 @@ export function createDokployClient(config: DokployConfig) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async function deleteDomain(domainId: string): Promise<void> {
|
async function deleteDomain(domainId: string): Promise<void> {
|
||||||
await call('/api/domain.delete', { domainId })
|
await call('/domain.delete', { domainId })
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- High-level: provision a full instance ---
|
// --- High-level: provision a full instance ---
|
||||||
|
|
||||||
async function provisionInstance(input: ProvisionInstanceInput): Promise<ProvisionInstanceResult> {
|
async function provisionInstance(input: ProvisionInstanceInput): Promise<ProvisionInstanceResult> {
|
||||||
// 1. Create application from Git repo
|
const environmentId = config.environmentId ?? ''
|
||||||
|
if (!environmentId) {
|
||||||
|
throw new DokployError('DOKPLOY_ENVIRONMENT_ID not configured', 500, 'config')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Create application
|
||||||
const app = await createApplication({
|
const app = await createApplication({
|
||||||
name: input.name,
|
name: input.name,
|
||||||
gitRepo: input.gitRepo,
|
environmentId,
|
||||||
gitBranch: input.gitBranch,
|
sourceType: input.gitProvider ?? 'gitea',
|
||||||
buildPath: '/',
|
serverId: config.serverId,
|
||||||
envVars: input.envVars,
|
description: `Tenant instance: ${input.subdomain}`,
|
||||||
cpuLimit: input.cpuLimit,
|
|
||||||
memoryLimit: input.memoryLimit,
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// 2. (Optional) Create Postgres database
|
// 2. Save Git provider (repo connection)
|
||||||
let database: DokployDatabase | undefined
|
// Parse git repo URL: https://git.dfmpanel.eu/dfmsolutions/AOS.git
|
||||||
if (input.databaseName) {
|
// → owner: dfmsolutions, repo: AOS
|
||||||
database = await createPostgresDatabase({
|
const gitMatch = input.gitRepo.match(/\/([^/]+)\/([^/]+?)(?:\.git)?$/)
|
||||||
name: input.databaseName,
|
const owner = gitMatch?.[1] ?? ''
|
||||||
appName: input.name,
|
const repo = gitMatch?.[2] ?? ''
|
||||||
|
|
||||||
|
if (input.gitProvider === 'github' || input.gitRepo.includes('github.com')) {
|
||||||
|
await saveGithubProvider({
|
||||||
|
applicationId: app.applicationId,
|
||||||
|
githubOwner: owner,
|
||||||
|
githubRepository: repo,
|
||||||
|
githubBranch: input.gitBranch,
|
||||||
|
githubBuildPath: input.buildPath ?? '/',
|
||||||
})
|
})
|
||||||
// Inject DATABASE_URL into the app's env
|
} else if (input.gitProvider === 'gitea' || input.gitRepo.includes('git.dfmpanel.eu')) {
|
||||||
const dbUrl = `postgresql://${database.username}:${database.password}@${database.host}:${database.port}/${database.database}`
|
await saveGiteaProvider({
|
||||||
await updateEnv(app.applicationId, {
|
applicationId: app.applicationId,
|
||||||
...input.envVars,
|
giteaOwner: owner,
|
||||||
DATABASE_URL: dbUrl,
|
giteaRepository: repo,
|
||||||
|
giteaBranch: input.gitBranch,
|
||||||
|
giteaBuildPath: input.buildPath ?? '/',
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Generic git
|
||||||
|
await saveGitProvider({
|
||||||
|
applicationId: app.applicationId,
|
||||||
|
gitProviderOwner: owner,
|
||||||
|
gitProviderRepository: repo,
|
||||||
|
gitProviderBranch: input.gitBranch,
|
||||||
|
gitProviderBuildPath: input.buildPath ?? '/',
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. Attach domain (subdomain.platform-domain)
|
// 3. Save environment variables
|
||||||
const hostName = input.subdomain
|
await saveEnvironment(app.applicationId, input.envVars)
|
||||||
const domain = await createDomain({
|
|
||||||
applicationId: app.applicationId,
|
|
||||||
hostName,
|
|
||||||
port: 3000,
|
|
||||||
https: true,
|
|
||||||
})
|
|
||||||
|
|
||||||
// 4. Trigger deploy
|
// 4. Deploy
|
||||||
await deployApplication(app.applicationId)
|
await deployApplication(app.applicationId)
|
||||||
|
|
||||||
|
// 5. Attach domain (optional — can be done later)
|
||||||
|
let domain: DokployDomain | undefined
|
||||||
|
try {
|
||||||
|
domain = await createDomain({
|
||||||
|
applicationId: app.applicationId,
|
||||||
|
hostName: input.subdomain,
|
||||||
|
port: 3000,
|
||||||
|
https: true,
|
||||||
|
})
|
||||||
|
} catch (e) {
|
||||||
|
// Domain creation is non-fatal — app is already deployed
|
||||||
|
console.warn('[dokploy] Domain creation failed (non-fatal):', e instanceof Error ? e.message : e)
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
app,
|
app,
|
||||||
database,
|
|
||||||
domain,
|
domain,
|
||||||
url: `https://${hostName}`,
|
url: `https://${input.subdomain}`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -315,14 +348,13 @@ export function createDokployClient(config: DokployConfig) {
|
|||||||
|
|
||||||
async function teardownInstance(input: {
|
async function teardownInstance(input: {
|
||||||
dokployAppId?: string
|
dokployAppId?: string
|
||||||
dokployDbId?: string
|
dokployDomainId?: string
|
||||||
dnsDomainId?: string // Dokploy domain ID
|
|
||||||
}): Promise<{ steps: string[] }> {
|
}): Promise<{ steps: string[] }> {
|
||||||
const steps: string[] = []
|
const steps: string[] = []
|
||||||
// Remove domain first (stop traffic)
|
// Remove domain first (stop traffic)
|
||||||
if (input.dnsDomainId) {
|
if (input.dokployDomainId) {
|
||||||
try {
|
try {
|
||||||
await deleteDomain(input.dnsDomainId)
|
await deleteDomain(input.dokployDomainId)
|
||||||
steps.push('domain.removed')
|
steps.push('domain.removed')
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
if (e instanceof DokployError && e.status !== 404) {
|
if (e instanceof DokployError && e.status !== 404) {
|
||||||
@@ -330,17 +362,6 @@ export function createDokployClient(config: DokployConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Delete database
|
|
||||||
if (input.dokployDbId) {
|
|
||||||
try {
|
|
||||||
await deleteDatabase(input.dokployDbId)
|
|
||||||
steps.push('database.removed')
|
|
||||||
} catch (e) {
|
|
||||||
if (e instanceof DokployError && e.status !== 404) {
|
|
||||||
steps.push(`database.error: ${e.message}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Delete application (+ volumes)
|
// Delete application (+ volumes)
|
||||||
if (input.dokployAppId) {
|
if (input.dokployAppId) {
|
||||||
try {
|
try {
|
||||||
@@ -358,14 +379,16 @@ export function createDokployClient(config: DokployConfig) {
|
|||||||
return {
|
return {
|
||||||
// Low-level
|
// Low-level
|
||||||
createApplication,
|
createApplication,
|
||||||
|
saveGiteaProvider,
|
||||||
|
saveGitProvider,
|
||||||
|
saveGithubProvider,
|
||||||
|
saveEnvironment,
|
||||||
deployApplication,
|
deployApplication,
|
||||||
getApplication,
|
getApplication,
|
||||||
deleteApplication,
|
deleteApplication,
|
||||||
stopApplication,
|
stopApplication,
|
||||||
startApplication,
|
startApplication,
|
||||||
updateEnv,
|
redeployApplication,
|
||||||
createPostgresDatabase,
|
|
||||||
deleteDatabase,
|
|
||||||
createDomain,
|
createDomain,
|
||||||
deleteDomain,
|
deleteDomain,
|
||||||
call,
|
call,
|
||||||
@@ -377,8 +400,6 @@ export function createDokployClient(config: DokployConfig) {
|
|||||||
|
|
||||||
export type DokployClient = ReturnType<typeof createDokployClient>
|
export type DokployClient = ReturnType<typeof createDokployClient>
|
||||||
|
|
||||||
// --- Factory: create from env ---
|
|
||||||
|
|
||||||
export function createDokployClientFromEnv(): DokployClient | null {
|
export function createDokployClientFromEnv(): DokployClient | null {
|
||||||
const url = process.env.DOKPLOY_URL
|
const url = process.env.DOKPLOY_URL
|
||||||
const apiKey = process.env.DOKPLOY_API_KEY
|
const apiKey = process.env.DOKPLOY_API_KEY
|
||||||
@@ -386,7 +407,7 @@ export function createDokployClientFromEnv(): DokployClient | null {
|
|||||||
return createDokployClient({
|
return createDokployClient({
|
||||||
url,
|
url,
|
||||||
apiKey,
|
apiKey,
|
||||||
serverId: process.env.DOKPLOY_SERVER_ID,
|
environmentId: process.env.DOKPLOY_ENVIRONMENT_ID,
|
||||||
projectId: process.env.DOKPLOY_PROJECT_ID,
|
serverId: process.env.DOKPLOY_SERVER_ID ?? null,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user