@aifabrix/builder 2.0.2 → 2.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -4
- package/lib/app-run.js +55 -165
- package/lib/build.js +3 -62
- package/lib/schema/env-config.yaml +9 -1
- package/lib/secrets.js +52 -194
- package/lib/utils/compose-generator.js +185 -0
- package/lib/utils/docker-build.js +173 -0
- package/lib/utils/health-check.js +26 -7
- package/lib/utils/secrets-generator.js +209 -0
- package/lib/validator.js +7 -3
- package/package.json +2 -1
- package/templates/applications/miso-controller/rbac.yaml +47 -1
- package/templates/applications/miso-controller/variables.yaml +10 -10
- package/templates/infra/compose.yaml +0 -2
- package/templates/python/docker-compose.hbs +22 -13
- package/templates/typescript/docker-compose.hbs +22 -13
|
@@ -71,10 +71,29 @@ async function waitForDbInit(appName) {
|
|
|
71
71
|
*/
|
|
72
72
|
async function getContainerPort(appName) {
|
|
73
73
|
try {
|
|
74
|
-
|
|
75
|
-
|
|
74
|
+
// Try to get the actual mapped host port from Docker
|
|
75
|
+
// First try docker inspect for the container port mapping
|
|
76
|
+
const { stdout: portMapping } = await execAsync(`docker inspect --format='{{range $p, $conf := .NetworkSettings.Ports}}{{if $conf}}{{range $conf}}{{.HostPort}}{{end}}{{end}}{{end}}' aifabrix-${appName}`);
|
|
77
|
+
const ports = portMapping.trim().split('\n').filter(p => p && p !== '');
|
|
76
78
|
if (ports.length > 0) {
|
|
77
|
-
|
|
79
|
+
const port = parseInt(ports[0], 10);
|
|
80
|
+
if (!isNaN(port) && port > 0) {
|
|
81
|
+
return port;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// Fallback: try docker ps to get port mapping (format: "0.0.0.0:3010->3000/tcp")
|
|
86
|
+
try {
|
|
87
|
+
const { stdout: psOutput } = await execAsync(`docker ps --filter "name=aifabrix-${appName}" --format "{{.Ports}}"`);
|
|
88
|
+
const portMatch = psOutput.match(/:(\d+)->/);
|
|
89
|
+
if (portMatch) {
|
|
90
|
+
const port = parseInt(portMatch[1], 10);
|
|
91
|
+
if (!isNaN(port) && port > 0) {
|
|
92
|
+
return port;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
} catch (error) {
|
|
96
|
+
// Fall through
|
|
78
97
|
}
|
|
79
98
|
} catch (error) {
|
|
80
99
|
// Fall through to default
|
|
@@ -152,12 +171,12 @@ async function checkHealthEndpoint(healthCheckUrl) {
|
|
|
152
171
|
async function waitForHealthCheck(appName, timeout = 90, port = null, config = null) {
|
|
153
172
|
await waitForDbInit(appName);
|
|
154
173
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
174
|
+
// Always detect the actual port from Docker to ensure we use the correct mapped port
|
|
175
|
+
const detectedPort = await getContainerPort(appName);
|
|
176
|
+
const healthCheckPort = port || detectedPort;
|
|
158
177
|
|
|
159
178
|
const healthCheckPath = config?.healthCheck?.path || '/health';
|
|
160
|
-
const healthCheckUrl = `http://localhost:${
|
|
179
|
+
const healthCheckUrl = `http://localhost:${healthCheckPort}${healthCheckPath}`;
|
|
161
180
|
const maxAttempts = timeout / 2;
|
|
162
181
|
|
|
163
182
|
for (let attempts = 0; attempts < maxAttempts; attempts++) {
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* AI Fabrix Builder Secrets Generation Utilities
|
|
3
|
+
*
|
|
4
|
+
* This module handles secret generation and file management.
|
|
5
|
+
* Generates default secret values and manages secrets files.
|
|
6
|
+
*
|
|
7
|
+
* @fileoverview Secret generation utilities for AI Fabrix Builder
|
|
8
|
+
* @author AI Fabrix Team
|
|
9
|
+
* @version 2.0.0
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const path = require('path');
|
|
14
|
+
const yaml = require('js-yaml');
|
|
15
|
+
const os = require('os');
|
|
16
|
+
const crypto = require('crypto');
|
|
17
|
+
const logger = require('./logger');
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Finds missing secret keys from template
|
|
21
|
+
* @function findMissingSecretKeys
|
|
22
|
+
* @param {string} envTemplate - Environment template content
|
|
23
|
+
* @param {Object} existingSecrets - Existing secrets object
|
|
24
|
+
* @returns {string[]} Array of missing secret keys
|
|
25
|
+
*/
|
|
26
|
+
function findMissingSecretKeys(envTemplate, existingSecrets) {
|
|
27
|
+
const kvPattern = /kv:\/\/([a-zA-Z0-9-_]+)/g;
|
|
28
|
+
const missingKeys = [];
|
|
29
|
+
const seenKeys = new Set();
|
|
30
|
+
|
|
31
|
+
let match;
|
|
32
|
+
while ((match = kvPattern.exec(envTemplate)) !== null) {
|
|
33
|
+
const secretKey = match[1];
|
|
34
|
+
if (!seenKeys.has(secretKey) && !(secretKey in existingSecrets)) {
|
|
35
|
+
missingKeys.push(secretKey);
|
|
36
|
+
seenKeys.add(secretKey);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return missingKeys;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Generates secret value based on key name
|
|
45
|
+
* @function generateSecretValue
|
|
46
|
+
* @param {string} key - Secret key name
|
|
47
|
+
* @returns {string} Generated secret value
|
|
48
|
+
*/
|
|
49
|
+
function generateSecretValue(key) {
|
|
50
|
+
const keyLower = key.toLowerCase();
|
|
51
|
+
|
|
52
|
+
if (keyLower.includes('password')) {
|
|
53
|
+
const dbPasswordMatch = key.match(/^databases-([a-z0-9-_]+)-\d+-passwordKeyVault$/i);
|
|
54
|
+
if (dbPasswordMatch) {
|
|
55
|
+
const appName = dbPasswordMatch[1];
|
|
56
|
+
const dbName = appName.replace(/-/g, '_');
|
|
57
|
+
return `${dbName}_pass123`;
|
|
58
|
+
}
|
|
59
|
+
return crypto.randomBytes(32).toString('base64');
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (keyLower.includes('url') || keyLower.includes('uri')) {
|
|
63
|
+
const dbUrlMatch = key.match(/^databases-([a-z0-9-_]+)-\d+-urlKeyVault$/i);
|
|
64
|
+
if (dbUrlMatch) {
|
|
65
|
+
const appName = dbUrlMatch[1];
|
|
66
|
+
const dbName = appName.replace(/-/g, '_');
|
|
67
|
+
return `postgresql://${dbName}_user:${dbName}_pass123@\${DB_HOST}:5432/${dbName}`;
|
|
68
|
+
}
|
|
69
|
+
return '';
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (keyLower.includes('key') || keyLower.includes('secret') || keyLower.includes('token')) {
|
|
73
|
+
return crypto.randomBytes(32).toString('base64');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
return '';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Loads existing secrets from file
|
|
81
|
+
* @function loadExistingSecrets
|
|
82
|
+
* @param {string} resolvedPath - Path to secrets file
|
|
83
|
+
* @returns {Object} Existing secrets object
|
|
84
|
+
*/
|
|
85
|
+
function loadExistingSecrets(resolvedPath) {
|
|
86
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const content = fs.readFileSync(resolvedPath, 'utf8');
|
|
92
|
+
const secrets = yaml.load(content) || {};
|
|
93
|
+
return typeof secrets === 'object' ? secrets : {};
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.warn(`Warning: Could not read existing secrets file: ${error.message}`);
|
|
96
|
+
return {};
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Saves secrets file
|
|
102
|
+
* @function saveSecretsFile
|
|
103
|
+
* @param {string} resolvedPath - Path to secrets file
|
|
104
|
+
* @param {Object} secrets - Secrets object to save
|
|
105
|
+
* @throws {Error} If save fails
|
|
106
|
+
*/
|
|
107
|
+
function saveSecretsFile(resolvedPath, secrets) {
|
|
108
|
+
const dir = path.dirname(resolvedPath);
|
|
109
|
+
if (!fs.existsSync(dir)) {
|
|
110
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const yamlContent = yaml.dump(secrets, {
|
|
114
|
+
indent: 2,
|
|
115
|
+
lineWidth: 120,
|
|
116
|
+
noRefs: true,
|
|
117
|
+
sortKeys: false
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
fs.writeFileSync(resolvedPath, yamlContent, { mode: 0o600 });
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Generates missing secret keys in secrets file
|
|
125
|
+
* Scans env.template for kv:// references and adds missing keys with secure defaults
|
|
126
|
+
*
|
|
127
|
+
* @async
|
|
128
|
+
* @function generateMissingSecrets
|
|
129
|
+
* @param {string} envTemplate - Environment template content
|
|
130
|
+
* @param {string} secretsPath - Path to secrets file
|
|
131
|
+
* @returns {Promise<string[]>} Array of newly generated secret keys
|
|
132
|
+
* @throws {Error} If generation fails
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* const newKeys = await generateMissingSecrets(template, '~/.aifabrix/secrets.yaml');
|
|
136
|
+
* // Returns: ['new-secret-key', 'another-secret']
|
|
137
|
+
*/
|
|
138
|
+
async function generateMissingSecrets(envTemplate, secretsPath) {
|
|
139
|
+
const resolvedPath = secretsPath || path.join(os.homedir(), '.aifabrix', 'secrets.yaml');
|
|
140
|
+
const existingSecrets = loadExistingSecrets(resolvedPath);
|
|
141
|
+
const missingKeys = findMissingSecretKeys(envTemplate, existingSecrets);
|
|
142
|
+
|
|
143
|
+
if (missingKeys.length === 0) {
|
|
144
|
+
return [];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const newSecrets = {};
|
|
148
|
+
for (const key of missingKeys) {
|
|
149
|
+
newSecrets[key] = generateSecretValue(key);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const updatedSecrets = { ...existingSecrets, ...newSecrets };
|
|
153
|
+
saveSecretsFile(resolvedPath, updatedSecrets);
|
|
154
|
+
|
|
155
|
+
logger.log(`✓ Generated ${missingKeys.length} missing secret key(s): ${missingKeys.join(', ')}`);
|
|
156
|
+
return missingKeys;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Creates default secrets file if it doesn't exist
|
|
161
|
+
* Generates template with common secrets for local development
|
|
162
|
+
*
|
|
163
|
+
* @async
|
|
164
|
+
* @function createDefaultSecrets
|
|
165
|
+
* @param {string} secretsPath - Path where to create secrets file
|
|
166
|
+
* @returns {Promise<void>} Resolves when file is created
|
|
167
|
+
* @throws {Error} If file creation fails
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* await createDefaultSecrets('~/.aifabrix/secrets.yaml');
|
|
171
|
+
* // Default secrets file is created
|
|
172
|
+
*/
|
|
173
|
+
async function createDefaultSecrets(secretsPath) {
|
|
174
|
+
const resolvedPath = secretsPath.startsWith('~')
|
|
175
|
+
? path.join(os.homedir(), secretsPath.slice(1))
|
|
176
|
+
: secretsPath;
|
|
177
|
+
|
|
178
|
+
const dir = path.dirname(resolvedPath);
|
|
179
|
+
if (!fs.existsSync(dir)) {
|
|
180
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const defaultSecrets = `# Local Development Secrets
|
|
184
|
+
# Production uses Azure KeyVault
|
|
185
|
+
|
|
186
|
+
# Database Secrets
|
|
187
|
+
postgres-passwordKeyVault: "admin123"
|
|
188
|
+
|
|
189
|
+
# Redis Secrets
|
|
190
|
+
redis-passwordKeyVault: ""
|
|
191
|
+
redis-urlKeyVault: "redis://\${REDIS_HOST}:6379"
|
|
192
|
+
|
|
193
|
+
# Keycloak Secrets
|
|
194
|
+
keycloak-admin-passwordKeyVault: "admin123"
|
|
195
|
+
keycloak-auth-server-urlKeyVault: "http://\${KEYCLOAK_HOST}:8082"
|
|
196
|
+
`;
|
|
197
|
+
|
|
198
|
+
fs.writeFileSync(resolvedPath, defaultSecrets, { mode: 0o600 });
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
module.exports = {
|
|
202
|
+
findMissingSecretKeys,
|
|
203
|
+
generateSecretValue,
|
|
204
|
+
loadExistingSecrets,
|
|
205
|
+
saveSecretsFile,
|
|
206
|
+
generateMissingSecrets,
|
|
207
|
+
createDefaultSecrets
|
|
208
|
+
};
|
|
209
|
+
|
package/lib/validator.js
CHANGED
|
@@ -185,13 +185,17 @@ async function validateEnvTemplate(appName) {
|
|
|
185
185
|
const lines = content.split('\n');
|
|
186
186
|
lines.forEach((line, index) => {
|
|
187
187
|
const trimmed = line.trim();
|
|
188
|
+
// Skip empty lines and comments
|
|
188
189
|
if (trimmed && !trimmed.startsWith('#')) {
|
|
189
190
|
if (!trimmed.includes('=')) {
|
|
190
191
|
errors.push(`Line ${index + 1}: Invalid environment variable format (missing =)`);
|
|
191
192
|
} else {
|
|
192
|
-
const [key,
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
const [key, _value] = trimmed.split('=', 2);
|
|
194
|
+
// Trim key to handle whitespace issues
|
|
195
|
+
// Empty values are allowed (_value can be empty string or undefined)
|
|
196
|
+
const trimmedKey = key ? key.trim() : '';
|
|
197
|
+
if (!trimmedKey) {
|
|
198
|
+
errors.push(`Line ${index + 1}: Invalid environment variable format (missing variable name)`);
|
|
195
199
|
}
|
|
196
200
|
}
|
|
197
201
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@aifabrix/builder",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "AI Fabrix Local Fabric & Deployment SDK",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
"lint:ci": "eslint . --ext .js --format json --output-file eslint-report.json",
|
|
19
19
|
"dev": "node bin/aifabrix.js",
|
|
20
20
|
"build": "npm run lint && npm run test:ci",
|
|
21
|
+
"pack": "npm run build && npm pack",
|
|
21
22
|
"validate": "npm run build",
|
|
22
23
|
"prepublishOnly": "npm run validate",
|
|
23
24
|
"precommit": "npm run lint:fix && npm run test"
|
|
@@ -52,6 +52,53 @@ permissions:
|
|
|
52
52
|
roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
|
|
53
53
|
description: "Deactivate service users"
|
|
54
54
|
|
|
55
|
+
# User Management
|
|
56
|
+
- name: "users:create"
|
|
57
|
+
roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
|
|
58
|
+
description: "Create new users"
|
|
59
|
+
|
|
60
|
+
- name: "users:read"
|
|
61
|
+
roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-observer"]
|
|
62
|
+
description: "View user information and profiles"
|
|
63
|
+
|
|
64
|
+
- name: "users:update"
|
|
65
|
+
roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
|
|
66
|
+
description: "Update user information and manage group memberships"
|
|
67
|
+
|
|
68
|
+
- name: "users:delete"
|
|
69
|
+
roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
|
|
70
|
+
description: "Delete users"
|
|
71
|
+
|
|
72
|
+
# Group Management
|
|
73
|
+
- name: "groups:create"
|
|
74
|
+
roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
|
|
75
|
+
description: "Create new groups"
|
|
76
|
+
|
|
77
|
+
- name: "groups:read"
|
|
78
|
+
roles: ["aifabrix-platform-admin", "aifabrix-security-admin", "aifabrix-observer"]
|
|
79
|
+
description: "View group information and members"
|
|
80
|
+
|
|
81
|
+
- name: "groups:update"
|
|
82
|
+
roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
|
|
83
|
+
description: "Update group information"
|
|
84
|
+
|
|
85
|
+
- name: "groups:delete"
|
|
86
|
+
roles: ["aifabrix-platform-admin", "aifabrix-security-admin"]
|
|
87
|
+
description: "Delete groups"
|
|
88
|
+
|
|
89
|
+
# Administrative Permissions
|
|
90
|
+
- name: "admin:read"
|
|
91
|
+
roles: ["aifabrix-platform-admin"]
|
|
92
|
+
description: "Administrative read access to all resources"
|
|
93
|
+
|
|
94
|
+
- name: "admin:write"
|
|
95
|
+
roles: ["aifabrix-platform-admin"]
|
|
96
|
+
description: "Administrative write access to all resources"
|
|
97
|
+
|
|
98
|
+
- name: "admin:delete"
|
|
99
|
+
roles: ["aifabrix-platform-admin"]
|
|
100
|
+
description: "Administrative delete access to all resources"
|
|
101
|
+
|
|
55
102
|
# Template Applications (environment = null)
|
|
56
103
|
- name: "applications:create"
|
|
57
104
|
roles: ["aifabrix-platform-admin", "aifabrix-infrastructure-admin", "aifabrix-deployment-admin"]
|
|
@@ -165,4 +212,3 @@ permissions:
|
|
|
165
212
|
- name: "dashboard:read"
|
|
166
213
|
roles: ["aifabrix-platform-admin", "aifabrix-deployment-admin", "aifabrix-developer", "aifabrix-observer"]
|
|
167
214
|
description: "View dashboard summaries and aggregates"
|
|
168
|
-
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Application Metadata
|
|
2
2
|
app:
|
|
3
|
-
key: miso
|
|
3
|
+
key: miso-controller
|
|
4
4
|
displayName: "Miso Controller"
|
|
5
5
|
description: "AI Fabrix Miso Controller - Backend API and orchestration service"
|
|
6
6
|
type: webapp
|
|
@@ -36,21 +36,21 @@ healthCheck:
|
|
|
36
36
|
authentication:
|
|
37
37
|
type: keycloak
|
|
38
38
|
enableSSO: true
|
|
39
|
-
requiredRoles:
|
|
39
|
+
requiredRoles:
|
|
40
|
+
- aifabrix-user
|
|
40
41
|
endpoints:
|
|
41
|
-
local:
|
|
42
|
+
local: http://localhost:3000/auth/callback
|
|
42
43
|
|
|
43
44
|
# Build Configuration
|
|
44
45
|
build:
|
|
45
|
-
context: ..
|
|
46
|
-
dockerfile: builder/Dockerfile
|
|
47
|
-
envOutputPath:
|
|
48
|
-
localPort: 3010
|
|
49
|
-
language: typescript
|
|
50
|
-
secrets:
|
|
46
|
+
context: .. # Docker build context (relative to builder/)
|
|
47
|
+
dockerfile: builder/miso-controller/Dockerfile # Dockerfile name (empty = use template)
|
|
48
|
+
envOutputPath: # Copy .env to repo root for local dev (relative to builder/) (if null, no .env file is copied) (if empty, .env file is copied to repo root)
|
|
49
|
+
localPort: 3010 # Port for local development (different from Docker port)
|
|
50
|
+
language: typescript # Runtime language for template selection (typescript or python)
|
|
51
|
+
secrets: # Path to secrets file
|
|
51
52
|
|
|
52
53
|
# Docker Compose
|
|
53
54
|
compose:
|
|
54
55
|
file: docker-compose.yaml
|
|
55
56
|
service: miso-controller
|
|
56
|
-
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
# Generated by AI Fabrix Builder SDK
|
|
3
3
|
# Service definition for local development
|
|
4
4
|
|
|
5
|
-
version: "3.9"
|
|
6
|
-
|
|
7
5
|
services:
|
|
8
6
|
{{app.key}}:
|
|
9
7
|
image: {{image.name}}:{{image.tag}}
|
|
@@ -48,23 +46,34 @@ services:
|
|
|
48
46
|
command: >
|
|
49
47
|
sh -c "
|
|
50
48
|
export PGHOST=postgres PGPORT=5432 PGUSER=pgadmin &&
|
|
51
|
-
export PGPASSWORD
|
|
49
|
+
export PGPASSWORD=\"${POSTGRES_PASSWORD}\" &&
|
|
50
|
+
echo 'Waiting for PostgreSQL to be ready...' &&
|
|
51
|
+
counter=0 &&
|
|
52
|
+
while [ $counter -lt 30 ]; do
|
|
53
|
+
if pg_isready -h postgres -p 5432 -U pgadmin >/dev/null 2>&1; then
|
|
54
|
+
echo 'PostgreSQL is ready!'
|
|
55
|
+
break
|
|
56
|
+
fi
|
|
57
|
+
echo 'Waiting for PostgreSQL...'
|
|
58
|
+
sleep 1
|
|
59
|
+
counter=$((counter + 1))
|
|
60
|
+
done &&
|
|
52
61
|
{{#if databases}}
|
|
53
62
|
{{#each databases}}
|
|
54
63
|
echo 'Creating {{name}} database and user...' &&
|
|
55
|
-
psql -d postgres -c 'CREATE DATABASE {{name}};' ||
|
|
56
|
-
psql -d postgres -c \"CREATE USER {{name}}_user WITH PASSWORD '{{name}}_pass123';\" ||
|
|
57
|
-
psql -d postgres -c 'GRANT ALL PRIVILEGES ON DATABASE {{name}} TO {{name}}_user;' &&
|
|
58
|
-
psql -d {{name}} -c 'ALTER SCHEMA public OWNER TO {{name}}_user;' &&
|
|
59
|
-
psql -d {{name}} -c 'GRANT ALL ON SCHEMA public TO {{name}}_user;' &&
|
|
64
|
+
(psql -d postgres -c 'CREATE DATABASE {{name}};' || true) &&
|
|
65
|
+
(psql -d postgres -c \"CREATE USER {{name}}_user WITH PASSWORD '{{name}}_pass123';\" || true) &&
|
|
66
|
+
psql -d postgres -c 'GRANT ALL PRIVILEGES ON DATABASE {{name}} TO {{name}}_user;' || true &&
|
|
67
|
+
psql -d {{name}} -c 'ALTER SCHEMA public OWNER TO {{name}}_user;' || true &&
|
|
68
|
+
psql -d {{name}} -c 'GRANT ALL ON SCHEMA public TO {{name}}_user;' || true &&
|
|
60
69
|
{{/each}}
|
|
61
70
|
{{else}}
|
|
62
71
|
echo 'Creating {{app.key}} database and user...' &&
|
|
63
|
-
psql -d postgres -c 'CREATE DATABASE {{app.key}};' ||
|
|
64
|
-
psql -d postgres -c \"CREATE USER {{app.key}}_user WITH PASSWORD '{{app.key}}_pass123';\" ||
|
|
65
|
-
psql -d postgres -c 'GRANT ALL PRIVILEGES ON DATABASE {{app.key}} TO {{app.key}}_user;' &&
|
|
66
|
-
psql -d {{app.key}} -c 'ALTER SCHEMA public OWNER TO {{app.key}}_user;' &&
|
|
67
|
-
psql -d {{app.key}} -c 'GRANT ALL ON SCHEMA public TO {{app.key}}_user;' &&
|
|
72
|
+
(psql -d postgres -c 'CREATE DATABASE {{app.key}};' || true) &&
|
|
73
|
+
(psql -d postgres -c \"CREATE USER {{app.key}}_user WITH PASSWORD '{{app.key}}_pass123';\" || true) &&
|
|
74
|
+
psql -d postgres -c 'GRANT ALL PRIVILEGES ON DATABASE {{app.key}} TO {{app.key}}_user;' || true &&
|
|
75
|
+
psql -d {{app.key}} -c 'ALTER SCHEMA public OWNER TO {{app.key}}_user;' || true &&
|
|
76
|
+
psql -d {{app.key}} -c 'GRANT ALL ON SCHEMA public TO {{app.key}}_user;' || true &&
|
|
68
77
|
{{/if}}
|
|
69
78
|
echo 'Database initialization complete!'
|
|
70
79
|
"
|
|
@@ -2,8 +2,6 @@
|
|
|
2
2
|
# Generated by AI Fabrix Builder SDK
|
|
3
3
|
# Service definition for local development
|
|
4
4
|
|
|
5
|
-
version: "3.9"
|
|
6
|
-
|
|
7
5
|
services:
|
|
8
6
|
{{app.key}}:
|
|
9
7
|
image: {{image.name}}:{{image.tag}}
|
|
@@ -48,23 +46,34 @@ services:
|
|
|
48
46
|
command: >
|
|
49
47
|
sh -c "
|
|
50
48
|
export PGHOST=postgres PGPORT=5432 PGUSER=pgadmin &&
|
|
51
|
-
export PGPASSWORD
|
|
49
|
+
export PGPASSWORD=\"${POSTGRES_PASSWORD}\" &&
|
|
50
|
+
echo 'Waiting for PostgreSQL to be ready...' &&
|
|
51
|
+
counter=0 &&
|
|
52
|
+
while [ $counter -lt 30 ]; do
|
|
53
|
+
if pg_isready -h postgres -p 5432 -U pgadmin >/dev/null 2>&1; then
|
|
54
|
+
echo 'PostgreSQL is ready!'
|
|
55
|
+
break
|
|
56
|
+
fi
|
|
57
|
+
echo 'Waiting for PostgreSQL...'
|
|
58
|
+
sleep 1
|
|
59
|
+
counter=$((counter + 1))
|
|
60
|
+
done &&
|
|
52
61
|
{{#if databases}}
|
|
53
62
|
{{#each databases}}
|
|
54
63
|
echo 'Creating {{name}} database and user...' &&
|
|
55
|
-
psql -d postgres -c 'CREATE DATABASE {{name}};' ||
|
|
56
|
-
psql -d postgres -c \"CREATE USER {{name}}_user WITH PASSWORD '{{name}}_pass123';\" ||
|
|
57
|
-
psql -d postgres -c 'GRANT ALL PRIVILEGES ON DATABASE {{name}} TO {{name}}_user;' &&
|
|
58
|
-
psql -d {{name}} -c 'ALTER SCHEMA public OWNER TO {{name}}_user;' &&
|
|
59
|
-
psql -d {{name}} -c 'GRANT ALL ON SCHEMA public TO {{name}}_user;' &&
|
|
64
|
+
(psql -d postgres -c 'CREATE DATABASE {{name}};' || true) &&
|
|
65
|
+
(psql -d postgres -c \"CREATE USER {{name}}_user WITH PASSWORD '{{name}}_pass123';\" || true) &&
|
|
66
|
+
psql -d postgres -c 'GRANT ALL PRIVILEGES ON DATABASE {{name}} TO {{name}}_user;' || true &&
|
|
67
|
+
psql -d {{name}} -c 'ALTER SCHEMA public OWNER TO {{name}}_user;' || true &&
|
|
68
|
+
psql -d {{name}} -c 'GRANT ALL ON SCHEMA public TO {{name}}_user;' || true &&
|
|
60
69
|
{{/each}}
|
|
61
70
|
{{else}}
|
|
62
71
|
echo 'Creating {{app.key}} database and user...' &&
|
|
63
|
-
psql -d postgres -c 'CREATE DATABASE {{app.key}};' ||
|
|
64
|
-
psql -d postgres -c \"CREATE USER {{app.key}}_user WITH PASSWORD '{{app.key}}_pass123';\" ||
|
|
65
|
-
psql -d postgres -c 'GRANT ALL PRIVILEGES ON DATABASE {{app.key}} TO {{app.key}}_user;' &&
|
|
66
|
-
psql -d {{app.key}} -c 'ALTER SCHEMA public OWNER TO {{app.key}}_user;' &&
|
|
67
|
-
psql -d {{app.key}} -c 'GRANT ALL ON SCHEMA public TO {{app.key}}_user;' &&
|
|
72
|
+
(psql -d postgres -c 'CREATE DATABASE {{app.key}};' || true) &&
|
|
73
|
+
(psql -d postgres -c \"CREATE USER {{app.key}}_user WITH PASSWORD '{{app.key}}_pass123';\" || true) &&
|
|
74
|
+
psql -d postgres -c 'GRANT ALL PRIVILEGES ON DATABASE {{app.key}} TO {{app.key}}_user;' || true &&
|
|
75
|
+
psql -d {{app.key}} -c 'ALTER SCHEMA public OWNER TO {{app.key}}_user;' || true &&
|
|
76
|
+
psql -d {{app.key}} -c 'GRANT ALL ON SCHEMA public TO {{app.key}}_user;' || true &&
|
|
68
77
|
{{/if}}
|
|
69
78
|
echo 'Database initialization complete!'
|
|
70
79
|
"
|