feat: add WordPress as runtime — Dockerfile, K8s env vars, wp-content PVC, frontend UI

Backend:
- Add WORDPRESS to AppRuntime enum
- Add wordpressDockerfile() using wordpress:6-php8.3-apache base image
  with custom theme/plugin/wp-content merge support
- Add WordPress-specific K8s env vars (WORDPRESS_DB_HOST, WORDPRESS_DB_USER,
  WORDPRESS_DB_PASSWORD, WORDPRESS_DB_NAME, WORDPRESS_TABLE_PREFIX)
- Create wp-content PersistentVolumeClaim (2Gi) for WordPress deployments
- Mount wp-content PVC in deployment container at /var/www/html/wp-content

Frontend:
- Add 'wordpress' to runtime type unions (Application, CreateApplicationDto)
- Add WordPress runtime card in deploy page (port 80, blue icon)
- Auto-select MySQL database when WordPress is chosen, disable other DB options
- Show Persian hint 'وردپرس به MySQL نیاز دارد' when WordPress selected
- Update all runtime icon colors across dashboard, apps, admin/apps pages
  to show blue-600 for WordPress
This commit is contained in:
keyhan
2026-04-06 23:13:39 +03:30
parent ea86acf2ab
commit c411c5873e
10 changed files with 134 additions and 15 deletions
@@ -115,6 +115,11 @@ export class KubernetesService implements OnModuleInit {
manifests.database = await this.deployDatabase(coreApi, appsApi, context);
}
// 3.5 Create wp-content PVC for WordPress
if (context.runtime === AppRuntime.WORDPRESS) {
manifests.wpContentPvc = await this.applyWordPressPvc(coreApi, context);
}
// 4. Create Deployment
manifests.deployment = await this.applyDeployment(appsApi, context);
@@ -194,6 +199,17 @@ export class KubernetesService implements OnModuleInit {
);
}
// WordPress-specific env vars (official image expects these)
if (ctx.runtime === AppRuntime.WORDPRESS && ctx.databaseType === DatabaseType.MYSQL) {
extraEnv.push(
{ name: 'WORDPRESS_DB_HOST', value: `${ctx.appName}-db:3306` },
{ name: 'WORDPRESS_DB_NAME', value: ctx.appName.replace(/-/g, '_') },
{ name: 'WORDPRESS_DB_USER', valueFrom: { secretKeyRef: { name: `${ctx.appName}-db-secret`, key: 'username' } } },
{ name: 'WORDPRESS_DB_PASSWORD', valueFrom: { secretKeyRef: { name: `${ctx.appName}-db-secret`, key: 'password' } } },
{ name: 'WORDPRESS_TABLE_PREFIX', value: 'wp_' },
);
}
const deployment: k8s.V1Deployment = {
apiVersion: 'apps/v1',
kind: 'Deployment',
@@ -230,8 +246,30 @@ export class KubernetesService implements OnModuleInit {
periodSeconds: 10,
failureThreshold: 5,
},
...(ctx.runtime === AppRuntime.WORDPRESS
? {
volumeMounts: [
{
name: 'wp-content',
mountPath: '/var/www/html/wp-content',
},
],
}
: {}),
},
],
...(ctx.runtime === AppRuntime.WORDPRESS
? {
volumes: [
{
name: 'wp-content',
persistentVolumeClaim: {
claimName: `${ctx.appName}-wp-content`,
},
},
],
}
: {}),
},
},
},
@@ -245,6 +283,34 @@ export class KubernetesService implements OnModuleInit {
return deployment;
}
private async applyWordPressPvc(coreApi: k8s.CoreV1Api, ctx: ManifestContext): Promise<any> {
const pvcName = `${ctx.appName}-wp-content`;
const pvc = {
apiVersion: 'v1',
kind: 'PersistentVolumeClaim',
metadata: {
name: pvcName,
namespace: ctx.namespace,
labels: { app: ctx.appName },
},
spec: {
accessModes: ['ReadWriteOnce'],
resources: {
requests: { storage: '2Gi' },
},
},
};
try {
await coreApi.readNamespacedPersistentVolumeClaim(pvcName, ctx.namespace);
this.logger.log(`PVC ${pvcName} already exists, skipping creation`);
} catch {
await coreApi.createNamespacedPersistentVolumeClaim(ctx.namespace, pvc);
this.logger.log(`Created WordPress PVC: ${pvcName}`);
}
return pvc;
}
private async applyService(coreApi: k8s.CoreV1Api, ctx: ManifestContext): Promise<any> {
const service: k8s.V1Service = {
apiVersion: 'v1',