underpost 2.95.3 → 2.95.8
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 -2
- package/bin/deploy.js +6 -26
- package/cli.md +13 -13
- package/conf.js +4 -1
- package/examples/{QUICK-REFERENCE.md → static-page/QUICK-REFERENCE.md} +0 -18
- package/examples/{README.md → static-page/README.md} +3 -44
- package/examples/{STATIC-GENERATOR-GUIDE.md → static-page/STATIC-GENERATOR-GUIDE.md} +0 -50
- package/examples/{ssr-components → static-page/ssr-components}/CustomPage.js +0 -13
- package/examples/{static-config-simple.json → static-page/static-config-example.json} +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/package.json +1 -1
- package/src/cli/baremetal.js +19 -10
- package/src/cli/cloud-init.js +10 -4
- package/src/cli/cron.js +161 -29
- package/src/cli/db.js +59 -92
- package/src/cli/index.js +3 -58
- package/src/cli/repository.js +14 -0
- package/src/cli/run.js +2 -3
- package/src/cli/static.js +99 -194
- package/src/client/services/default/default.management.js +7 -0
- package/src/index.js +1 -1
- package/src/server/backup.js +4 -53
- package/src/server/conf.js +3 -4
- package/examples/static-config-example.json +0 -183
package/src/cli/cron.js
CHANGED
|
@@ -4,12 +4,10 @@
|
|
|
4
4
|
* @namespace UnderpostCron
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { DataBaseProvider } from '../db/DataBaseProvider.js';
|
|
8
7
|
import BackUp from '../server/backup.js';
|
|
9
8
|
import { Cmd } from '../server/conf.js';
|
|
10
9
|
import Dns from '../server/dns.js';
|
|
11
10
|
import { loggerFactory } from '../server/logger.js';
|
|
12
|
-
|
|
13
11
|
import { shellExec } from '../server/process.js';
|
|
14
12
|
import fs from 'fs-extra';
|
|
15
13
|
|
|
@@ -37,57 +35,191 @@ class UnderpostCron {
|
|
|
37
35
|
*/
|
|
38
36
|
backup: BackUp,
|
|
39
37
|
};
|
|
38
|
+
|
|
40
39
|
static API = {
|
|
41
40
|
/**
|
|
42
41
|
* Run the cron jobs
|
|
43
42
|
* @static
|
|
44
43
|
* @param {String} deployList - Comma separated deploy ids
|
|
45
44
|
* @param {String} jobList - Comma separated job ids
|
|
45
|
+
* @param {Object} options - Options for cron execution
|
|
46
46
|
* @return {void}
|
|
47
47
|
* @memberof UnderpostCron
|
|
48
48
|
*/
|
|
49
49
|
callback: async function (
|
|
50
50
|
deployList = 'default',
|
|
51
|
-
jobList = Object.keys(UnderpostCron.JOB),
|
|
52
|
-
options = {
|
|
51
|
+
jobList = Object.keys(UnderpostCron.JOB).join(','),
|
|
52
|
+
options = { initPm2Cronjobs: false, git: false, updatePackageScripts: false },
|
|
53
53
|
) {
|
|
54
|
-
if (options.
|
|
55
|
-
|
|
56
|
-
deployList = fs.readFileSync('./engine-private/deploy/dd.router', 'utf8').trim();
|
|
57
|
-
const confCronConfig = JSON.parse(fs.readFileSync(`./engine-private/conf/${jobDeployId}/conf.cron.json`));
|
|
58
|
-
if (confCronConfig.jobs && Object.keys(confCronConfig.jobs).length > 0) {
|
|
59
|
-
for (const job of Object.keys(confCronConfig.jobs)) {
|
|
60
|
-
const name = `${jobDeployId}-${job}`;
|
|
61
|
-
let deployId;
|
|
62
|
-
shellExec(Cmd.delete(name));
|
|
63
|
-
deployId = UnderpostCron.API.getRelatedDeployId(job);
|
|
64
|
-
shellExec(Cmd.cron(deployId, job, name, confCronConfig.jobs[job].expression, options));
|
|
65
|
-
}
|
|
66
|
-
}
|
|
54
|
+
if (options.updatePackageScripts === true) {
|
|
55
|
+
await UnderpostCron.API.updatePackageScripts(deployList);
|
|
67
56
|
return;
|
|
68
57
|
}
|
|
58
|
+
|
|
59
|
+
if (options.initPm2Cronjobs === true) {
|
|
60
|
+
await UnderpostCron.API.initCronJobs(options);
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Execute the requested jobs
|
|
69
65
|
for (const _jobId of jobList.split(',')) {
|
|
70
66
|
const jobId = _jobId.trim();
|
|
71
|
-
if (UnderpostCron.JOB[jobId])
|
|
67
|
+
if (UnderpostCron.JOB[jobId]) {
|
|
68
|
+
logger.info(`Executing cron job: ${jobId}`);
|
|
69
|
+
await UnderpostCron.JOB[jobId].callback(deployList, options);
|
|
70
|
+
} else {
|
|
71
|
+
logger.warn(`Unknown cron job: ${jobId}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Initialize PM2 cron jobs from configuration
|
|
78
|
+
* @static
|
|
79
|
+
* @param {Object} options - Initialization options
|
|
80
|
+
* @memberof UnderpostCron
|
|
81
|
+
*/
|
|
82
|
+
initCronJobs: async function (options = { git: false }) {
|
|
83
|
+
logger.info('Initializing PM2 cron jobs');
|
|
84
|
+
|
|
85
|
+
// Read cron job deployment ID from dd.cron file (e.g., "dd-cron")
|
|
86
|
+
const jobDeployId = fs.readFileSync('./engine-private/deploy/dd.cron', 'utf8').trim();
|
|
87
|
+
const confCronPath = `./engine-private/conf/${jobDeployId}/conf.cron.json`;
|
|
88
|
+
|
|
89
|
+
if (!fs.existsSync(confCronPath)) {
|
|
90
|
+
logger.warn(`Cron configuration not found: ${confCronPath}`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const confCronConfig = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
|
|
95
|
+
|
|
96
|
+
if (!confCronConfig.jobs || Object.keys(confCronConfig.jobs).length === 0) {
|
|
97
|
+
logger.info('No cron jobs configured');
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Delete all existing cron jobs
|
|
102
|
+
for (const job of Object.keys(confCronConfig.jobs)) {
|
|
103
|
+
const name = `${jobDeployId}-${job}`;
|
|
104
|
+
logger.info(`Removing existing PM2 process: ${name}`);
|
|
105
|
+
shellExec(Cmd.delete(name));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Create PM2 cron jobs for each configured job
|
|
109
|
+
for (const job of Object.keys(confCronConfig.jobs)) {
|
|
110
|
+
const jobConfig = confCronConfig.jobs[job];
|
|
111
|
+
|
|
112
|
+
if (jobConfig.enabled === false) {
|
|
113
|
+
logger.info(`Skipping disabled job: ${job}`);
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
const name = `${jobDeployId}-${job}`;
|
|
118
|
+
const deployIdList = UnderpostCron.API.getRelatedDeployIdList(job);
|
|
119
|
+
const expression = jobConfig.expression || '0 0 * * *'; // Default: daily at midnight
|
|
120
|
+
const instances = jobConfig.instances || 1; // Default: 1 instance
|
|
121
|
+
|
|
122
|
+
logger.info(`Creating PM2 cron job: ${name} with expression: ${expression}, instances: ${instances}`);
|
|
123
|
+
shellExec(Cmd.cron(deployIdList, job, name, expression, options, instances));
|
|
72
124
|
}
|
|
125
|
+
|
|
126
|
+
logger.info('PM2 cron jobs initialization completed');
|
|
73
127
|
},
|
|
74
128
|
|
|
75
129
|
/**
|
|
76
|
-
*
|
|
130
|
+
* Update package.json start scripts for specified deploy-ids
|
|
77
131
|
* @static
|
|
78
|
-
* @param {String}
|
|
79
|
-
* @return {String} The related deploy id
|
|
132
|
+
* @param {String} deployList - Comma separated deploy ids
|
|
80
133
|
* @memberof UnderpostCron
|
|
81
134
|
*/
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
135
|
+
updatePackageScripts: async function (deployList = 'default') {
|
|
136
|
+
logger.info('Updating package.json start scripts for deploy-id configurations');
|
|
137
|
+
|
|
138
|
+
// Resolve deploy list
|
|
139
|
+
if ((!deployList || deployList === 'dd') && fs.existsSync(`./engine-private/deploy/dd.router`)) {
|
|
140
|
+
deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').trim();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const confDir = './engine-private/conf';
|
|
144
|
+
if (!fs.existsSync(confDir)) {
|
|
145
|
+
logger.warn(`Configuration directory not found: ${confDir}`);
|
|
146
|
+
return;
|
|
90
147
|
}
|
|
148
|
+
|
|
149
|
+
// Parse deploy list into array
|
|
150
|
+
const deployIds = deployList
|
|
151
|
+
.split(',')
|
|
152
|
+
.map((id) => id.trim())
|
|
153
|
+
.filter((id) => id);
|
|
154
|
+
|
|
155
|
+
for (const deployId of deployIds) {
|
|
156
|
+
const packageJsonPath = `${confDir}/${deployId}/package.json`;
|
|
157
|
+
const confCronPath = `${confDir}/${deployId}/conf.cron.json`;
|
|
158
|
+
|
|
159
|
+
// Only update if both package.json and conf.cron.json exist
|
|
160
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
161
|
+
logger.info(`Skipping ${deployId}: package.json not found`);
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!fs.existsSync(confCronPath)) {
|
|
166
|
+
logger.info(`Skipping ${deployId}: conf.cron.json not found`);
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
171
|
+
const confCron = JSON.parse(fs.readFileSync(confCronPath, 'utf8'));
|
|
172
|
+
|
|
173
|
+
// Build start script based on cron jobs configuration
|
|
174
|
+
if (confCron.jobs && Object.keys(confCron.jobs).length > 0) {
|
|
175
|
+
const hasEnabledJobs = Object.values(confCron.jobs).some((job) => job.enabled !== false);
|
|
176
|
+
|
|
177
|
+
if (hasEnabledJobs) {
|
|
178
|
+
// Update start script with PM2 cron jobs initialization
|
|
179
|
+
const startScript = 'pm2 flush && pm2 reloadLogs && node bin cron --init-pm2-cronjobs --git';
|
|
180
|
+
|
|
181
|
+
if (!packageJson.scripts) {
|
|
182
|
+
packageJson.scripts = {};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
packageJson.scripts.start = startScript;
|
|
186
|
+
|
|
187
|
+
// Write updated package.json
|
|
188
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 4) + '\n', 'utf8');
|
|
189
|
+
logger.info(`Updated package.json for ${deployId} with cron start script`);
|
|
190
|
+
} else {
|
|
191
|
+
logger.info(`Skipping ${deployId}: no enabled cron jobs`);
|
|
192
|
+
}
|
|
193
|
+
} else {
|
|
194
|
+
logger.info(`Skipping ${deployId}: no cron jobs configured`);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
logger.info('Package.json start scripts update completed');
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Get the related deploy id list for the given job id
|
|
203
|
+
* @static
|
|
204
|
+
* @param {String} jobId - The job id (e.g., 'dns', 'backup')
|
|
205
|
+
* @return {String} Comma-separated list of deploy ids to process
|
|
206
|
+
* @memberof UnderpostCron
|
|
207
|
+
*/
|
|
208
|
+
getRelatedDeployIdList(jobId) {
|
|
209
|
+
// Backup job uses dd.router file (contains multiple deploy-ids)
|
|
210
|
+
// Other jobs use dd.cron file (contains single deploy-id)
|
|
211
|
+
const deployFilePath =
|
|
212
|
+
jobId === 'backup' ? './engine-private/deploy/dd.router' : './engine-private/deploy/dd.cron';
|
|
213
|
+
|
|
214
|
+
if (!fs.existsSync(deployFilePath)) {
|
|
215
|
+
logger.warn(`Deploy file not found: ${deployFilePath}, using default`);
|
|
216
|
+
return fs.existsSync('./engine-private/deploy/dd.cron')
|
|
217
|
+
? fs.readFileSync('./engine-private/deploy/dd.cron', 'utf8').trim()
|
|
218
|
+
: 'dd-cron';
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// Return the deploy-id list from the file (may be single or comma-separated)
|
|
222
|
+
return fs.readFileSync(deployFilePath, 'utf8').trim();
|
|
91
223
|
},
|
|
92
224
|
};
|
|
93
225
|
}
|
package/src/cli/db.js
CHANGED
|
@@ -31,7 +31,6 @@ const MAX_BACKUP_RETENTION = 5;
|
|
|
31
31
|
* @property {boolean} [import=false] - Flag to import data from a backup
|
|
32
32
|
* @property {boolean} [export=false] - Flag to export data to a backup
|
|
33
33
|
* @property {string} [podName=''] - Comma-separated list of pod names or patterns
|
|
34
|
-
* @property {string} [nodeName=''] - Comma-separated list of node names for pod filtering
|
|
35
34
|
* @property {string} [ns='default'] - Kubernetes namespace
|
|
36
35
|
* @property {string} [collections=''] - Comma-separated list of collections to include
|
|
37
36
|
* @property {string} [outPath=''] - Output path for backup files
|
|
@@ -40,7 +39,6 @@ const MAX_BACKUP_RETENTION = 5;
|
|
|
40
39
|
* @property {boolean} [git=false] - Flag to enable Git integration
|
|
41
40
|
* @property {string} [hosts=''] - Comma-separated list of hosts to include
|
|
42
41
|
* @property {string} [paths=''] - Comma-separated list of paths to include
|
|
43
|
-
* @property {string} [labelSelector=''] - Kubernetes label selector for pods
|
|
44
42
|
* @property {boolean} [allPods=false] - Flag to target all matching pods
|
|
45
43
|
* @property {boolean} [primaryPod=false] - Flag to automatically detect and use MongoDB primary pod
|
|
46
44
|
* @property {boolean} [stats=false] - Flag to display collection/table statistics
|
|
@@ -78,43 +76,17 @@ const MAX_BACKUP_RETENTION = 5;
|
|
|
78
76
|
*/
|
|
79
77
|
class UnderpostDB {
|
|
80
78
|
static API = {
|
|
81
|
-
/**
|
|
82
|
-
* Helper: Validates namespace name
|
|
83
|
-
* @private
|
|
84
|
-
* @param {string} namespace - Namespace to validate
|
|
85
|
-
* @returns {boolean} True if valid
|
|
86
|
-
*/
|
|
87
|
-
_validateNamespace(namespace) {
|
|
88
|
-
if (!namespace || typeof namespace !== 'string') return false;
|
|
89
|
-
// Kubernetes namespace naming rules: lowercase alphanumeric, -, max 63 chars
|
|
90
|
-
return /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(namespace) && namespace.length <= 63;
|
|
91
|
-
},
|
|
92
|
-
|
|
93
|
-
/**
|
|
94
|
-
* Helper: Validates pod name
|
|
95
|
-
* @private
|
|
96
|
-
* @param {string} podName - Pod name to validate
|
|
97
|
-
* @returns {boolean} True if valid
|
|
98
|
-
*/
|
|
99
|
-
_validatePodName(podName) {
|
|
100
|
-
if (!podName || typeof podName !== 'string') return false;
|
|
101
|
-
// Kubernetes pod naming rules: lowercase alphanumeric, -, max 253 chars
|
|
102
|
-
return /^[a-z0-9]([-a-z0-9]*[a-z0-9])?$/.test(podName) && podName.length <= 253;
|
|
103
|
-
},
|
|
104
|
-
|
|
105
79
|
/**
|
|
106
80
|
* Helper: Gets filtered pods based on criteria
|
|
107
81
|
* @private
|
|
108
82
|
* @param {Object} criteria - Filter criteria
|
|
109
83
|
* @param {string} [criteria.podNames] - Comma-separated pod name patterns
|
|
110
|
-
* @param {string} [criteria.nodeNames] - Comma-separated node names
|
|
111
84
|
* @param {string} [criteria.namespace='default'] - Kubernetes namespace
|
|
112
|
-
* @param {string} [criteria.labelSelector] - Label selector
|
|
113
85
|
* @param {string} [criteria.deployId] - Deployment ID pattern
|
|
114
86
|
* @returns {Array<PodInfo>} Filtered pod list
|
|
115
87
|
*/
|
|
116
88
|
_getFilteredPods(criteria = {}) {
|
|
117
|
-
const { podNames,
|
|
89
|
+
const { podNames, namespace = 'default', deployId } = criteria;
|
|
118
90
|
|
|
119
91
|
try {
|
|
120
92
|
// Get all pods using UnderpostDeploy.API.get
|
|
@@ -132,25 +104,6 @@ class UnderpostDB {
|
|
|
132
104
|
});
|
|
133
105
|
}
|
|
134
106
|
|
|
135
|
-
// Filter by node names if specified (only if NODE is not '<none>')
|
|
136
|
-
if (nodeNames) {
|
|
137
|
-
const nodes = nodeNames.split(',').map((n) => n.trim());
|
|
138
|
-
pods = pods.filter((pod) => {
|
|
139
|
-
// Skip filtering if NODE is '<none>' or undefined
|
|
140
|
-
if (!pod.NODE || pod.NODE === '<none>') {
|
|
141
|
-
return true;
|
|
142
|
-
}
|
|
143
|
-
return nodes.includes(pod.NODE);
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Filter by label selector if specified
|
|
148
|
-
if (labelSelector) {
|
|
149
|
-
// Note: UnderpostDeploy.API.get doesn't support label selectors directly
|
|
150
|
-
// This would require a separate kubectl command
|
|
151
|
-
logger.warn('Label selector filtering requires additional implementation');
|
|
152
|
-
}
|
|
153
|
-
|
|
154
107
|
logger.info(`Found ${pods.length} pod(s) matching criteria`, { criteria, podNames: pods.map((p) => p.NAME) });
|
|
155
108
|
return pods;
|
|
156
109
|
} catch (error) {
|
|
@@ -717,9 +670,6 @@ class UnderpostDB {
|
|
|
717
670
|
* @param {string} [options.podName='mongodb-0'] - Initial pod name to query replica set status
|
|
718
671
|
* @returns {string|null} Primary pod name or null if not found
|
|
719
672
|
* @memberof UnderpostDB
|
|
720
|
-
* @example
|
|
721
|
-
* const primaryPod = UnderpostDB.API.getMongoPrimaryPodName({ namespace: 'production' });
|
|
722
|
-
* console.log(primaryPod); // 'mongodb-1'
|
|
723
673
|
*/
|
|
724
674
|
getMongoPrimaryPodName(options = { namespace: 'default', podName: 'mongodb-0' }) {
|
|
725
675
|
const { namespace = 'default', podName = 'mongodb-0' } = options;
|
|
@@ -765,7 +715,6 @@ class UnderpostDB {
|
|
|
765
715
|
* @param {boolean} [options.import=false] - Whether to perform import operation
|
|
766
716
|
* @param {boolean} [options.export=false] - Whether to perform export operation
|
|
767
717
|
* @param {string} [options.podName=''] - Comma-separated pod name patterns to target
|
|
768
|
-
* @param {string} [options.nodeName=''] - Comma-separated node names to target
|
|
769
718
|
* @param {string} [options.ns='default'] - Kubernetes namespace
|
|
770
719
|
* @param {string} [options.collections=''] - Comma-separated MongoDB collections for export
|
|
771
720
|
* @param {string} [options.outPath=''] - Output path for backups
|
|
@@ -774,7 +723,6 @@ class UnderpostDB {
|
|
|
774
723
|
* @param {boolean} [options.git=false] - Whether to use Git for backup versioning
|
|
775
724
|
* @param {string} [options.hosts=''] - Comma-separated list of hosts to filter databases
|
|
776
725
|
* @param {string} [options.paths=''] - Comma-separated list of paths to filter databases
|
|
777
|
-
* @param {string} [options.labelSelector=''] - Label selector for pod filtering
|
|
778
726
|
* @param {boolean} [options.allPods=false] - Whether to target all pods in deployment
|
|
779
727
|
* @param {boolean} [options.primaryPod=false] - Whether to target MongoDB primary pod only
|
|
780
728
|
* @param {boolean} [options.stats=false] - Whether to display database statistics
|
|
@@ -788,7 +736,6 @@ class UnderpostDB {
|
|
|
788
736
|
import: false,
|
|
789
737
|
export: false,
|
|
790
738
|
podName: '',
|
|
791
|
-
nodeName: '',
|
|
792
739
|
ns: 'default',
|
|
793
740
|
collections: '',
|
|
794
741
|
outPath: '',
|
|
@@ -797,7 +744,6 @@ class UnderpostDB {
|
|
|
797
744
|
git: false,
|
|
798
745
|
hosts: '',
|
|
799
746
|
paths: '',
|
|
800
|
-
labelSelector: '',
|
|
801
747
|
allPods: false,
|
|
802
748
|
primaryPod: false,
|
|
803
749
|
stats: false,
|
|
@@ -808,12 +754,6 @@ class UnderpostDB {
|
|
|
808
754
|
const newBackupTimestamp = new Date().getTime();
|
|
809
755
|
const namespace = options.ns && typeof options.ns === 'string' ? options.ns : 'default';
|
|
810
756
|
|
|
811
|
-
// Validate namespace
|
|
812
|
-
if (!UnderpostDB.API._validateNamespace(namespace)) {
|
|
813
|
-
logger.error('Invalid namespace format', { namespace });
|
|
814
|
-
throw new Error(`Invalid namespace: ${namespace}`);
|
|
815
|
-
}
|
|
816
|
-
|
|
817
757
|
if (deployList === 'dd') deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
|
|
818
758
|
|
|
819
759
|
logger.info('Starting database operation', {
|
|
@@ -823,6 +763,11 @@ class UnderpostDB {
|
|
|
823
763
|
export: options.export,
|
|
824
764
|
});
|
|
825
765
|
|
|
766
|
+
// Track processed repositories to avoid duplicate Git operations
|
|
767
|
+
const processedRepos = new Set();
|
|
768
|
+
// Track processed host+path combinations to avoid duplicates
|
|
769
|
+
const processedHostPaths = new Set();
|
|
770
|
+
|
|
826
771
|
for (const _deployId of deployList.split(',')) {
|
|
827
772
|
const deployId = _deployId.trim();
|
|
828
773
|
if (!deployId) continue;
|
|
@@ -831,7 +776,7 @@ class UnderpostDB {
|
|
|
831
776
|
|
|
832
777
|
/** @type {Object.<string, Object.<string, DatabaseConfig>>} */
|
|
833
778
|
const dbs = {};
|
|
834
|
-
const repoName = `engine-${deployId.split('dd-')[1]}-cron-backups`;
|
|
779
|
+
const repoName = `engine-${deployId.includes('dd-') ? deployId.split('dd-')[1] : deployId}-cron-backups`;
|
|
835
780
|
|
|
836
781
|
// Load server configuration
|
|
837
782
|
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
@@ -863,31 +808,42 @@ class UnderpostDB {
|
|
|
863
808
|
}
|
|
864
809
|
}
|
|
865
810
|
|
|
866
|
-
// Handle Git operations
|
|
867
|
-
if (
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
811
|
+
// Handle Git operations - execute only once per repository
|
|
812
|
+
if (!processedRepos.has(repoName)) {
|
|
813
|
+
logger.info('Processing Git operations for repository', { repoName, deployId });
|
|
814
|
+
if (options.git === true) {
|
|
815
|
+
UnderpostDB.API._manageGitRepo({ repoName, operation: 'clone', forceClone: options.forceClone });
|
|
816
|
+
UnderpostDB.API._manageGitRepo({ repoName, operation: 'pull' });
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
if (options.macroRollbackExport) {
|
|
820
|
+
// Only clone if not already done by git option above
|
|
821
|
+
if (options.git !== true) {
|
|
822
|
+
UnderpostDB.API._manageGitRepo({ repoName, operation: 'clone', forceClone: options.forceClone });
|
|
823
|
+
UnderpostDB.API._manageGitRepo({ repoName, operation: 'pull' });
|
|
824
|
+
}
|
|
871
825
|
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
} else {
|
|
888
|
-
if (!username) logger.error('GITHUB_USERNAME environment variable not set');
|
|
889
|
-
logger.warn('Repository not found for macro rollback', { repoPath });
|
|
826
|
+
const nCommits = parseInt(options.macroRollbackExport);
|
|
827
|
+
const repoPath = `../${repoName}`;
|
|
828
|
+
const username = process.env.GITHUB_USERNAME;
|
|
829
|
+
|
|
830
|
+
if (fs.existsSync(repoPath) && username) {
|
|
831
|
+
logger.info('Executing macro rollback export', { repoName, nCommits });
|
|
832
|
+
shellExec(`cd ${repoPath} && underpost cmt . reset ${nCommits}`);
|
|
833
|
+
shellExec(`cd ${repoPath} && git reset`);
|
|
834
|
+
shellExec(`cd ${repoPath} && git checkout .`);
|
|
835
|
+
shellExec(`cd ${repoPath} && git clean -f -d`);
|
|
836
|
+
shellExec(`cd ${repoPath} && underpost push . ${username}/${repoName} -f`);
|
|
837
|
+
} else {
|
|
838
|
+
if (!username) logger.error('GITHUB_USERNAME environment variable not set');
|
|
839
|
+
logger.warn('Repository not found for macro rollback', { repoPath });
|
|
840
|
+
}
|
|
890
841
|
}
|
|
842
|
+
|
|
843
|
+
processedRepos.add(repoName);
|
|
844
|
+
logger.info('Repository marked as processed', { repoName });
|
|
845
|
+
} else {
|
|
846
|
+
logger.info('Skipping Git operations for already processed repository', { repoName, deployId });
|
|
891
847
|
}
|
|
892
848
|
|
|
893
849
|
// Process each database provider
|
|
@@ -895,6 +851,15 @@ class UnderpostDB {
|
|
|
895
851
|
for (const dbName of Object.keys(dbs[provider])) {
|
|
896
852
|
const { hostFolder, user, password, host, path } = dbs[provider][dbName];
|
|
897
853
|
|
|
854
|
+
// Create unique identifier for host+path combination
|
|
855
|
+
const hostPathKey = `${deployId}:${host}:${path}`;
|
|
856
|
+
|
|
857
|
+
// Skip if this host+path combination was already processed
|
|
858
|
+
if (processedHostPaths.has(hostPathKey)) {
|
|
859
|
+
logger.info('Skipping already processed host/path', { dbName, host, path, deployId });
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
|
|
898
863
|
// Filter by hosts and paths if specified
|
|
899
864
|
if (
|
|
900
865
|
(options.hosts &&
|
|
@@ -917,7 +882,7 @@ class UnderpostDB {
|
|
|
917
882
|
continue;
|
|
918
883
|
}
|
|
919
884
|
|
|
920
|
-
logger.info('Processing database', { hostFolder, provider, dbName });
|
|
885
|
+
logger.info('Processing database', { hostFolder, provider, dbName, deployId });
|
|
921
886
|
|
|
922
887
|
const backUpPath = `../${repoName}/${hostFolder}`;
|
|
923
888
|
const backupInfo = UnderpostDB.API._manageBackupTimestamps(
|
|
@@ -949,16 +914,14 @@ class UnderpostDB {
|
|
|
949
914
|
let targetPods = [];
|
|
950
915
|
const podCriteria = {
|
|
951
916
|
podNames: options.podName,
|
|
952
|
-
nodeNames: options.nodeName,
|
|
953
917
|
namespace,
|
|
954
|
-
labelSelector: options.labelSelector,
|
|
955
918
|
deployId: provider === 'mariadb' ? 'mariadb' : 'mongo',
|
|
956
919
|
};
|
|
957
920
|
|
|
958
921
|
targetPods = UnderpostDB.API._getFilteredPods(podCriteria);
|
|
959
922
|
|
|
960
923
|
// Fallback to default if no custom pods specified
|
|
961
|
-
if (targetPods.length === 0 && !options.podName
|
|
924
|
+
if (targetPods.length === 0 && !options.podName) {
|
|
962
925
|
const defaultPods = UnderpostDeploy.API.get(
|
|
963
926
|
provider === 'mariadb' ? 'mariadb' : 'mongo',
|
|
964
927
|
'pods',
|
|
@@ -1094,16 +1057,20 @@ class UnderpostDB {
|
|
|
1094
1057
|
break;
|
|
1095
1058
|
}
|
|
1096
1059
|
}
|
|
1060
|
+
|
|
1061
|
+
// Mark this host+path combination as processed
|
|
1062
|
+
processedHostPaths.add(hostPathKey);
|
|
1097
1063
|
}
|
|
1098
1064
|
}
|
|
1099
1065
|
|
|
1100
|
-
// Commit and push to Git if enabled
|
|
1101
|
-
if (options.export === true && options.git === true) {
|
|
1066
|
+
// Commit and push to Git if enabled - execute only once per repository
|
|
1067
|
+
if (options.export === true && options.git === true && !processedRepos.has(`${repoName}-committed`)) {
|
|
1102
1068
|
const commitMessage = `${new Date(newBackupTimestamp).toLocaleDateString()} ${new Date(
|
|
1103
1069
|
newBackupTimestamp,
|
|
1104
1070
|
).toLocaleTimeString()}`;
|
|
1105
1071
|
UnderpostDB.API._manageGitRepo({ repoName, operation: 'commit', message: commitMessage });
|
|
1106
1072
|
UnderpostDB.API._manageGitRepo({ repoName, operation: 'push' });
|
|
1073
|
+
processedRepos.add(`${repoName}-committed`);
|
|
1107
1074
|
}
|
|
1108
1075
|
}
|
|
1109
1076
|
|
|
@@ -1267,7 +1234,7 @@ class UnderpostDB {
|
|
|
1267
1234
|
for (const jobId of Object.keys(confCron.jobs)) {
|
|
1268
1235
|
const body = {
|
|
1269
1236
|
jobId,
|
|
1270
|
-
deployId: UnderpostCron.API.
|
|
1237
|
+
deployId: UnderpostCron.API.getRelatedDeployIdList(jobId),
|
|
1271
1238
|
expression: confCron.jobs[jobId].expression,
|
|
1272
1239
|
enabled: confCron.jobs[jobId].enabled,
|
|
1273
1240
|
};
|
package/src/cli/index.js
CHANGED
|
@@ -172,59 +172,7 @@ program
|
|
|
172
172
|
.option('--dev', 'Sets the development cli context')
|
|
173
173
|
|
|
174
174
|
.description(`Manages static build of page, bundles, and documentation with comprehensive customization options.`)
|
|
175
|
-
.action(
|
|
176
|
-
// Handle config template generation
|
|
177
|
-
if (options.generateConfig) {
|
|
178
|
-
const configPath = typeof options.generateConfig === 'string' ? options.generateConfig : './static-config.json';
|
|
179
|
-
return UnderpostStatic.API.generateConfigTemplate(configPath);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
// Parse comma-separated options
|
|
183
|
-
if (options.keywords) {
|
|
184
|
-
options.keywords = options.keywords.split(',').map((k) => k.trim());
|
|
185
|
-
}
|
|
186
|
-
if (options.headScripts) {
|
|
187
|
-
options.scripts = options.scripts || {};
|
|
188
|
-
options.scripts.head = options.headScripts.split(',').map((s) => ({ src: s.trim() }));
|
|
189
|
-
}
|
|
190
|
-
if (options.bodyScripts) {
|
|
191
|
-
options.scripts = options.scripts || {};
|
|
192
|
-
options.scripts.body = options.bodyScripts.split(',').map((s) => ({ src: s.trim() }));
|
|
193
|
-
}
|
|
194
|
-
if (options.styles) {
|
|
195
|
-
options.styles = options.styles.split(',').map((s) => ({ href: s.trim() }));
|
|
196
|
-
}
|
|
197
|
-
if (options.headComponents) {
|
|
198
|
-
options.headComponents = options.headComponents.split(',').map((c) => c.trim());
|
|
199
|
-
}
|
|
200
|
-
if (options.bodyComponents) {
|
|
201
|
-
options.bodyComponents = options.bodyComponents.split(',').map((c) => c.trim());
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Build metadata object from individual options
|
|
205
|
-
options.metadata = {
|
|
206
|
-
...(options.title && { title: options.title }),
|
|
207
|
-
...(options.description && { description: options.description }),
|
|
208
|
-
...(options.keywords && { keywords: options.keywords }),
|
|
209
|
-
...(options.author && { author: options.author }),
|
|
210
|
-
...(options.themeColor && { themeColor: options.themeColor }),
|
|
211
|
-
...(options.canonicalUrl && { canonicalURL: options.canonicalUrl }),
|
|
212
|
-
...(options.thumbnail && { thumbnail: options.thumbnail }),
|
|
213
|
-
...(options.locale && { locale: options.locale }),
|
|
214
|
-
...(options.siteName && { siteName: options.siteName }),
|
|
215
|
-
};
|
|
216
|
-
|
|
217
|
-
// Build icons object
|
|
218
|
-
if (options.favicon || options.appleTouchIcon || options.manifest) {
|
|
219
|
-
options.icons = {
|
|
220
|
-
...(options.favicon && { favicon: options.favicon }),
|
|
221
|
-
...(options.appleTouchIcon && { appleTouchIcon: options.appleTouchIcon }),
|
|
222
|
-
...(options.manifest && { manifest: options.manifest }),
|
|
223
|
-
};
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
return UnderpostStatic.API.callback(options);
|
|
227
|
-
});
|
|
175
|
+
.action(UnderpostStatic.API.callback);
|
|
228
176
|
|
|
229
177
|
// 'config' command: Manage Underpost configurations
|
|
230
178
|
program
|
|
@@ -417,8 +365,6 @@ program
|
|
|
417
365
|
'--pod-name <pod-name>',
|
|
418
366
|
'Comma-separated list of pod names or patterns (supports wildcards like "mariadb-*").',
|
|
419
367
|
)
|
|
420
|
-
.option('--node-name <node-name>', 'Comma-separated list of node names to filter pods by their node placement.')
|
|
421
|
-
.option('--label-selector <selector>', 'Kubernetes label selector for filtering pods (e.g., "app=mariadb").')
|
|
422
368
|
.option('--all-pods', 'Target all matching pods instead of just the first one.')
|
|
423
369
|
.option('--primary-pod', 'Automatically detect and use MongoDB primary pod (MongoDB only).')
|
|
424
370
|
.option('--stats', 'Display database statistics (collection/table names with document/row counts).')
|
|
@@ -479,9 +425,9 @@ program
|
|
|
479
425
|
', ',
|
|
480
426
|
)}. Defaults to all available jobs.`,
|
|
481
427
|
)
|
|
482
|
-
.option('--
|
|
483
|
-
.option('--init', 'Initializes cron jobs for the default deployment ID.')
|
|
428
|
+
.option('--init-pm2-cronjobs', 'Initializes PM2 cron jobs from configuration for the specified deployment IDs.')
|
|
484
429
|
.option('--git', 'Uploads cron job configurations to GitHub.')
|
|
430
|
+
.option('--update-package-scripts', 'Updates package.json start scripts for each deploy-id configuration.')
|
|
485
431
|
.description('Manages cron jobs, including initialization, execution, and configuration updates.')
|
|
486
432
|
.action(Underpost.cron.callback);
|
|
487
433
|
|
|
@@ -670,7 +616,6 @@ program
|
|
|
670
616
|
.option('--nfs-unmount', 'Unmounts the NFS root filesystem for a workflow id config architecture.')
|
|
671
617
|
.option('--nfs-sh', 'Copies QEMU emulation root entrypoint shell command to the clipboard.')
|
|
672
618
|
.option('--cloud-init-update', 'Updates cloud init for a workflow id config architecture.')
|
|
673
|
-
.option('--cloud-init-reset', 'Resets cloud init for a workflow id config architecture.')
|
|
674
619
|
.option('--logs <log-id>', 'Displays logs for log id: dhcp, cloud, machine, cloud-config.')
|
|
675
620
|
.option('--dev', 'Sets the development context environment for baremetal operations.')
|
|
676
621
|
.option('--ls', 'Lists available boot resources and machines.')
|
package/src/cli/repository.js
CHANGED
|
@@ -587,6 +587,20 @@ Prevent build private config repo.`,
|
|
|
587
587
|
fs.writeFileSync(targetConfPath, confRawPaths.join(sepRender), 'utf8');
|
|
588
588
|
shellExec(`prettier --write ${targetConfPath}`);
|
|
589
589
|
},
|
|
590
|
+
|
|
591
|
+
/**
|
|
592
|
+
* Cleans the specified paths in the repository by resetting, checking out, and cleaning untracked files.
|
|
593
|
+
* @param {object} [options={ paths: [''] }] - The options for cleaning.
|
|
594
|
+
* @param {string[]} [options.paths=['']] - The paths to clean.
|
|
595
|
+
* @memberof UnderpostRepository
|
|
596
|
+
*/
|
|
597
|
+
clean(options = { paths: [''] }) {
|
|
598
|
+
for (const path of options.paths) {
|
|
599
|
+
shellExec(`cd ${path} && git reset`, { silent: true });
|
|
600
|
+
shellExec(`cd ${path} && git checkout .`, { silent: true });
|
|
601
|
+
shellExec(`cd ${path} && git clean -f -d`, { silent: true });
|
|
602
|
+
}
|
|
603
|
+
},
|
|
590
604
|
};
|
|
591
605
|
}
|
|
592
606
|
|
package/src/cli/run.js
CHANGED
|
@@ -446,8 +446,7 @@ class UnderpostRun {
|
|
|
446
446
|
* @memberof UnderpostRun
|
|
447
447
|
*/
|
|
448
448
|
clean: (path = '', options = UnderpostRun.DEFAULT_OPTION) => {
|
|
449
|
-
|
|
450
|
-
shellExec(`node bin/deploy clean-core-repo`);
|
|
449
|
+
Underpost.repo.clean({ paths: path ? path.split(',') : ['/home/dd/engine', '/home/dd/engine/engine-private'] });
|
|
451
450
|
},
|
|
452
451
|
/**
|
|
453
452
|
* @method pull
|
|
@@ -1187,7 +1186,7 @@ EOF
|
|
|
1187
1186
|
}
|
|
1188
1187
|
await timer(5000);
|
|
1189
1188
|
for (const deployId of deployList) {
|
|
1190
|
-
shellExec(`${baseCommand} db ${deployId} --import --git`);
|
|
1189
|
+
shellExec(`${baseCommand} db ${deployId} --import --git --drop --preserveUUID --primary-pod`);
|
|
1191
1190
|
}
|
|
1192
1191
|
await timer(5000);
|
|
1193
1192
|
shellExec(`${baseCommand} cluster${baseClusterCommand} --${clusterType} --pull-image --valkey`);
|