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:
@@ -288,6 +288,8 @@ export class BuildService {
|
||||
return this.nodeDockerfile(app);
|
||||
case AppRuntime.LARAVEL:
|
||||
return this.laravelDockerfile(app);
|
||||
case AppRuntime.WORDPRESS:
|
||||
return this.wordpressDockerfile(app);
|
||||
default:
|
||||
throw new Error(`Unsupported runtime: ${app.runtime}`);
|
||||
}
|
||||
@@ -384,6 +386,39 @@ CMD ["/usr/bin/supervisord", "-c", "/etc/supervisord.conf"]
|
||||
`;
|
||||
}
|
||||
|
||||
private wordpressDockerfile(app: Application): string {
|
||||
return `FROM wordpress:6-php8.3-apache
|
||||
|
||||
# Install additional PHP extensions commonly needed by WordPress
|
||||
RUN docker-php-ext-install opcache
|
||||
|
||||
# Enable Apache mod_rewrite for pretty permalinks
|
||||
RUN a2enmod rewrite
|
||||
|
||||
# Copy user's custom themes, plugins, and uploads if provided
|
||||
COPY . /tmp/user-content
|
||||
|
||||
# Merge user content into the WordPress installation
|
||||
# - wp-content/themes, wp-content/plugins, wp-content/uploads
|
||||
# - Also support full WordPress roots (with wp-config.php, etc.)
|
||||
RUN if [ -d /tmp/user-content/wp-content ]; then \\
|
||||
cp -a /tmp/user-content/wp-content/. /var/www/html/wp-content/; \\
|
||||
fi && \\
|
||||
if [ -f /tmp/user-content/wp-config.php ]; then \\
|
||||
cp /tmp/user-content/wp-config.php /var/www/html/wp-config.php; \\
|
||||
fi && \\
|
||||
# Copy any loose PHP files (custom root files)
|
||||
find /tmp/user-content -maxdepth 1 -name "*.php" ! -name "wp-config.php" -exec cp {} /var/www/html/ \\\\; 2>/dev/null || true && \\
|
||||
rm -rf /tmp/user-content
|
||||
|
||||
# Set proper ownership
|
||||
RUN chown -R www-data:www-data /var/www/html
|
||||
|
||||
EXPOSE 80
|
||||
CMD ["apache2-foreground"]
|
||||
`;
|
||||
}
|
||||
|
||||
private async waitForJobCompletion(
|
||||
batchApi: k8s.BatchV1Api,
|
||||
coreApi: k8s.CoreV1Api,
|
||||
|
||||
@@ -28,6 +28,7 @@ export enum TicketPriority {
|
||||
export enum AppRuntime {
|
||||
NODEJS = 'nodejs',
|
||||
LARAVEL = 'laravel',
|
||||
WORDPRESS = 'wordpress',
|
||||
}
|
||||
|
||||
export enum DatabaseType {
|
||||
|
||||
@@ -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',
|
||||
|
||||
Reference in New Issue
Block a user