underpost 2.8.0 → 2.8.6
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/.dockerignore +1 -0
- package/.github/workflows/ghpkg.yml +19 -49
- package/.github/workflows/npmpkg.yml +67 -0
- package/.github/workflows/publish.yml +5 -5
- package/.github/workflows/pwa-microservices-template.page.yml +12 -4
- package/.github/workflows/pwa-microservices-template.test.yml +2 -2
- package/.vscode/extensions.json +17 -71
- package/.vscode/settings.json +20 -4
- package/AUTHORS.md +16 -5
- package/CHANGELOG.md +103 -3
- package/Dockerfile +24 -66
- package/README.md +1 -28
- package/bin/build.js +186 -0
- package/bin/db.js +2 -24
- package/bin/deploy.js +168 -157
- package/bin/file.js +59 -16
- package/bin/hwt.js +0 -10
- package/bin/index.js +201 -61
- package/bin/ssl.js +19 -11
- package/bin/util.js +24 -101
- package/bin/vs.js +26 -2
- package/conf.js +30 -132
- package/docker-compose.yml +1 -1
- package/manifests/deployment/mongo-express/deployment.yaml +60 -0
- package/manifests/deployment/phpmyadmin/deployment.yaml +54 -0
- package/manifests/kind-config-dev.yaml +12 -0
- package/manifests/kind-config.yaml +12 -0
- package/manifests/letsencrypt-prod.yaml +15 -0
- package/manifests/mariadb/config.yaml +10 -0
- package/manifests/mariadb/kustomization.yaml +9 -0
- package/manifests/mariadb/pv.yaml +12 -0
- package/manifests/mariadb/pvc.yaml +10 -0
- package/manifests/mariadb/secret.yaml +8 -0
- package/manifests/mariadb/service.yaml +10 -0
- package/manifests/mariadb/statefulset.yaml +55 -0
- package/manifests/mongodb/backup-access.yaml +16 -0
- package/manifests/mongodb/backup-cronjob.yaml +42 -0
- package/manifests/mongodb/backup-pv-pvc.yaml +22 -0
- package/manifests/mongodb/configmap.yaml +26 -0
- package/manifests/mongodb/headless-service.yaml +10 -0
- package/manifests/mongodb/kustomization.yaml +11 -0
- package/manifests/mongodb/pv-pvc.yaml +23 -0
- package/manifests/mongodb/statefulset.yaml +125 -0
- package/manifests/mongodb-4.4/kustomization.yaml +7 -0
- package/manifests/mongodb-4.4/pv-pvc.yaml +23 -0
- package/manifests/mongodb-4.4/service-deployment.yaml +63 -0
- package/manifests/valkey/kustomization.yaml +7 -0
- package/manifests/valkey/service.yaml +17 -0
- package/manifests/valkey/statefulset.yaml +39 -0
- package/package.json +133 -134
- package/src/api/core/core.service.js +1 -1
- package/src/api/user/user.model.js +16 -3
- package/src/api/user/user.service.js +1 -1
- package/src/cli/cluster.js +202 -0
- package/src/cli/cron.js +90 -0
- package/src/cli/db.js +212 -0
- package/src/cli/deploy.js +318 -0
- package/src/cli/env.js +52 -0
- package/src/cli/fs.js +149 -0
- package/src/cli/image.js +148 -0
- package/src/cli/repository.js +125 -0
- package/src/cli/script.js +53 -0
- package/src/cli/secrets.js +37 -0
- package/src/cli/test.js +118 -0
- package/src/client/components/core/Account.js +4 -2
- package/src/client/components/core/Auth.js +24 -6
- package/src/client/components/core/CalendarCore.js +127 -50
- package/src/client/components/core/CommonJs.js +282 -19
- package/src/client/components/core/Css.js +2 -1
- package/src/client/components/core/CssCore.js +8 -4
- package/src/client/components/core/Docs.js +1 -1
- package/src/client/components/core/DropDown.js +5 -1
- package/src/client/components/core/Input.js +22 -6
- package/src/client/components/core/JoyStick.js +8 -5
- package/src/client/components/core/LoadingAnimation.js +8 -1
- package/src/client/components/core/Modal.js +47 -18
- package/src/client/components/core/Panel.js +93 -31
- package/src/client/components/core/PanelForm.js +27 -19
- package/src/client/components/core/Scroll.js +1 -0
- package/src/client/components/core/SignUp.js +4 -1
- package/src/client/components/core/Translate.js +61 -9
- package/src/client/components/core/Validator.js +9 -1
- package/src/client/components/core/VanillaJs.js +0 -9
- package/src/client/components/core/Worker.js +34 -31
- package/src/client/public/default/plantuml/client-conf.svg +1 -1
- package/src/client/public/default/plantuml/server-conf.svg +1 -1
- package/src/client/public/default/plantuml/server-schema.svg +1 -1
- package/src/client/public/default/plantuml/ssr-conf.svg +1 -1
- package/src/client/public/default/plantuml/ssr-schema.svg +1 -1
- package/src/client/services/core/core.service.js +15 -8
- package/src/client/services/default/default.management.js +4 -2
- package/src/client/ssr/Render.js +4 -1
- package/src/client/ssr/body/CacheControl.js +2 -2
- package/src/client/ssr/body/DefaultSplashScreen.js +3 -3
- package/src/client/ssr/offline/Maintenance.js +63 -0
- package/src/client/sw/default.sw.js +26 -6
- package/src/db/mongo/MongooseDB.js +29 -1
- package/src/index.js +91 -17
- package/src/runtime/lampp/Lampp.js +1 -13
- package/src/runtime/xampp/Xampp.js +0 -13
- package/src/server/auth.js +3 -3
- package/src/server/backup.js +49 -93
- package/src/server/client-build.js +41 -50
- package/src/server/client-formatted.js +6 -3
- package/src/server/client-icons.js +1 -1
- package/src/server/conf.js +207 -57
- package/src/server/dns.js +30 -55
- package/src/server/downloader.js +0 -8
- package/src/server/logger.js +22 -15
- package/src/server/network.js +17 -43
- package/src/server/process.js +25 -2
- package/src/server/proxy.js +4 -26
- package/src/server/runtime.js +30 -30
- package/src/server/ssl.js +1 -1
- package/src/server/valkey.js +3 -0
- package/test/api.test.js +0 -8
- package/src/dns.js +0 -22
- package/src/server/prompt-optimizer.js +0 -28
- package/startup.js +0 -11
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import {
|
|
2
|
+
buildKindPorts,
|
|
3
|
+
buildPortProxyRouter,
|
|
4
|
+
buildProxyRouter,
|
|
5
|
+
Config,
|
|
6
|
+
getDataDeploy,
|
|
7
|
+
loadReplicas,
|
|
8
|
+
} from '../server/conf.js';
|
|
9
|
+
import { loggerFactory } from '../server/logger.js';
|
|
10
|
+
import { shellExec } from '../server/process.js';
|
|
11
|
+
import fs from 'fs-extra';
|
|
12
|
+
import dotenv from 'dotenv';
|
|
13
|
+
import Underpost from '../index.js';
|
|
14
|
+
|
|
15
|
+
const logger = loggerFactory(import.meta);
|
|
16
|
+
|
|
17
|
+
class UnderpostDeploy {
|
|
18
|
+
static API = {
|
|
19
|
+
sync(deployList) {
|
|
20
|
+
const deployGroupId = 'dd.tmp';
|
|
21
|
+
fs.writeFileSync(`./engine-private/deploy/${deployGroupId}`, deployList, 'utf8');
|
|
22
|
+
return getDataDeploy({
|
|
23
|
+
buildSingleReplica: true,
|
|
24
|
+
deployGroupId,
|
|
25
|
+
});
|
|
26
|
+
},
|
|
27
|
+
async routerFactory(deployList, env) {
|
|
28
|
+
const initEnvPath = `./engine-private/conf/${deployList.split(',')[0]}/.env.${env}`;
|
|
29
|
+
const initEnvObj = dotenv.parse(fs.readFileSync(initEnvPath, 'utf8'));
|
|
30
|
+
process.env.PORT = initEnvObj.PORT;
|
|
31
|
+
process.env.NODE_ENV = env;
|
|
32
|
+
await Config.build(undefined, 'proxy', deployList);
|
|
33
|
+
return buildPortProxyRouter(env === 'development' ? 80 : 443, buildProxyRouter());
|
|
34
|
+
},
|
|
35
|
+
async buildManifest(deployList, env, version) {
|
|
36
|
+
for (const _deployId of deployList.split(',')) {
|
|
37
|
+
const deployId = _deployId.trim();
|
|
38
|
+
if (!deployId) continue;
|
|
39
|
+
|
|
40
|
+
const router = await UnderpostDeploy.API.routerFactory(deployId, env);
|
|
41
|
+
const ports = Object.values(router).map((p) => parseInt(p.split(':')[2]));
|
|
42
|
+
const fromPort = Math.min(...ports);
|
|
43
|
+
const toPort = Math.max(...ports);
|
|
44
|
+
const confServer = loadReplicas(
|
|
45
|
+
JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
|
|
46
|
+
'proxy',
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
fs.mkdirSync(`./engine-private/conf/${deployId}/build/${env}`, { recursive: true });
|
|
50
|
+
if (env === 'development') fs.mkdirSync(`./manifests/deployment/${deployId}-${env}`, { recursive: true });
|
|
51
|
+
|
|
52
|
+
logger.info('port range', { deployId, fromPort, toPort });
|
|
53
|
+
|
|
54
|
+
const deploymentYamlParts = `apiVersion: apps/v1
|
|
55
|
+
kind: Deployment
|
|
56
|
+
metadata:
|
|
57
|
+
name: ${deployId}-${env}
|
|
58
|
+
labels:
|
|
59
|
+
app: ${deployId}-${env}
|
|
60
|
+
spec:
|
|
61
|
+
replicas: 2
|
|
62
|
+
selector:
|
|
63
|
+
matchLabels:
|
|
64
|
+
app: ${deployId}-${env}
|
|
65
|
+
template:
|
|
66
|
+
metadata:
|
|
67
|
+
labels:
|
|
68
|
+
app: ${deployId}-${env}
|
|
69
|
+
spec:
|
|
70
|
+
containers:
|
|
71
|
+
- name: ${deployId}-${env}
|
|
72
|
+
image: localhost/underpost-engine:${version && typeof version === 'string' ? version : Underpost.version}
|
|
73
|
+
lifecycle:
|
|
74
|
+
postStart:
|
|
75
|
+
exec:
|
|
76
|
+
command:
|
|
77
|
+
- /bin/sh
|
|
78
|
+
- -c
|
|
79
|
+
- >
|
|
80
|
+
sleep 60 &&
|
|
81
|
+
underpost config set deploy-id ${deployId} &&
|
|
82
|
+
underpost config set deploy-env ${env}
|
|
83
|
+
# image: localhost/${deployId}-${env}:${version && typeof version === 'string' ? version : Underpost.version}
|
|
84
|
+
---
|
|
85
|
+
apiVersion: v1
|
|
86
|
+
kind: Service
|
|
87
|
+
metadata:
|
|
88
|
+
name: ${deployId}-${env}-service
|
|
89
|
+
spec:
|
|
90
|
+
selector:
|
|
91
|
+
app: ${deployId}-${env}
|
|
92
|
+
ports:
|
|
93
|
+
type: LoadBalancer`.split('ports:');
|
|
94
|
+
deploymentYamlParts[1] =
|
|
95
|
+
buildKindPorts(fromPort, toPort) +
|
|
96
|
+
` type: LoadBalancer
|
|
97
|
+
`;
|
|
98
|
+
|
|
99
|
+
fs.writeFileSync(
|
|
100
|
+
`./engine-private/conf/${deployId}/build/${env}/deployment.yaml`,
|
|
101
|
+
deploymentYamlParts.join(`ports:
|
|
102
|
+
`),
|
|
103
|
+
);
|
|
104
|
+
|
|
105
|
+
let proxyYaml = '';
|
|
106
|
+
let secretYaml = '';
|
|
107
|
+
|
|
108
|
+
for (const host of Object.keys(confServer)) {
|
|
109
|
+
if (env === 'production')
|
|
110
|
+
secretYaml += `
|
|
111
|
+
---
|
|
112
|
+
apiVersion: cert-manager.io/v1
|
|
113
|
+
kind: Certificate
|
|
114
|
+
metadata:
|
|
115
|
+
name: ${host}
|
|
116
|
+
spec:
|
|
117
|
+
commonName: ${host}
|
|
118
|
+
dnsNames:
|
|
119
|
+
- ${host}
|
|
120
|
+
issuerRef:
|
|
121
|
+
name: letsencrypt-prod
|
|
122
|
+
kind: ClusterIssuer
|
|
123
|
+
secretName: ${host}`;
|
|
124
|
+
|
|
125
|
+
const pathPortConditions = [];
|
|
126
|
+
for (const path of Object.keys(confServer[host])) {
|
|
127
|
+
const { peer } = confServer[host][path];
|
|
128
|
+
if (!router[`${host}${path === '/' ? '' : path}`]) continue;
|
|
129
|
+
const port = parseInt(router[`${host}${path === '/' ? '' : path}`].split(':')[2]);
|
|
130
|
+
// logger.info('', { host, port, path });
|
|
131
|
+
pathPortConditions.push({
|
|
132
|
+
port,
|
|
133
|
+
path,
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (peer) {
|
|
137
|
+
// logger.info('', { host, port: port + 1, path: '/peer' });
|
|
138
|
+
pathPortConditions.push({
|
|
139
|
+
port: port + 1,
|
|
140
|
+
path: '/peer',
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// logger.info('', { host, pathPortConditions });
|
|
146
|
+
proxyYaml += `
|
|
147
|
+
---
|
|
148
|
+
apiVersion: projectcontour.io/v1
|
|
149
|
+
kind: HTTPProxy
|
|
150
|
+
metadata:
|
|
151
|
+
name: ${host}
|
|
152
|
+
spec:
|
|
153
|
+
virtualhost:
|
|
154
|
+
fqdn: ${host}${
|
|
155
|
+
env === 'development'
|
|
156
|
+
? ''
|
|
157
|
+
: `
|
|
158
|
+
tls:
|
|
159
|
+
secretName: ${host}`
|
|
160
|
+
}
|
|
161
|
+
routes:`;
|
|
162
|
+
for (const conditionObj of pathPortConditions) {
|
|
163
|
+
const { path, port } = conditionObj;
|
|
164
|
+
proxyYaml += `
|
|
165
|
+
- conditions:
|
|
166
|
+
- prefix: ${path}
|
|
167
|
+
enableWebsockets: true
|
|
168
|
+
services:
|
|
169
|
+
- name: ${deployId}-${env}-service
|
|
170
|
+
port: ${port}`;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const yamlPath = `./engine-private/conf/${deployId}/build/${env}/proxy.yaml`;
|
|
174
|
+
fs.writeFileSync(yamlPath, proxyYaml, 'utf8');
|
|
175
|
+
if (env === 'production') {
|
|
176
|
+
const yamlPath = `./engine-private/conf/${deployId}/build/${env}/secret.yaml`;
|
|
177
|
+
fs.writeFileSync(yamlPath, secretYaml, 'utf8');
|
|
178
|
+
} else {
|
|
179
|
+
const deploymentsFiles = ['Dockerfile', 'proxy.yaml', 'deployment.yaml'];
|
|
180
|
+
for (const file of deploymentsFiles) {
|
|
181
|
+
if (fs.existsSync(`./engine-private/conf/${deployId}/build/${env}/${file}`)) {
|
|
182
|
+
fs.copyFileSync(
|
|
183
|
+
`./engine-private/conf/${deployId}/build/${env}/${file}`,
|
|
184
|
+
`./manifests/deployment/${deployId}-${env}/${file}`,
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
async callback(
|
|
192
|
+
deployList = 'default',
|
|
193
|
+
env = 'development',
|
|
194
|
+
options = {
|
|
195
|
+
remove: false,
|
|
196
|
+
infoRouter: false,
|
|
197
|
+
sync: false,
|
|
198
|
+
buildManifest: false,
|
|
199
|
+
infoUtil: false,
|
|
200
|
+
expose: false,
|
|
201
|
+
cert: false,
|
|
202
|
+
version: '',
|
|
203
|
+
},
|
|
204
|
+
) {
|
|
205
|
+
if (options.infoUtil === true)
|
|
206
|
+
return logger.info(`
|
|
207
|
+
kubectl rollout restart deployment/deployment-name
|
|
208
|
+
kubectl rollout undo deployment/deployment-name
|
|
209
|
+
kubectl scale statefulsets <stateful-set-name> --replicas=<new-replicas>
|
|
210
|
+
`);
|
|
211
|
+
if (deployList === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`))
|
|
212
|
+
deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
|
|
213
|
+
if (options.sync) UnderpostDeploy.API.sync(deployList);
|
|
214
|
+
if (options.buildManifest === true) await UnderpostDeploy.API.buildManifest(deployList, env, options.version);
|
|
215
|
+
if (options.infoRouter === true)
|
|
216
|
+
return logger.info('router', await UnderpostDeploy.API.routerFactory(deployList, env));
|
|
217
|
+
const etcHost = (
|
|
218
|
+
concat,
|
|
219
|
+
) => `127.0.0.1 ${concat} localhost localhost.localdomain localhost4 localhost4.localdomain4
|
|
220
|
+
::1 localhost localhost.localdomain localhost6 localhost6.localdomain6`;
|
|
221
|
+
let concatHots = '';
|
|
222
|
+
|
|
223
|
+
for (const _deployId of deployList.split(',')) {
|
|
224
|
+
const deployId = _deployId.trim();
|
|
225
|
+
if (!deployId) continue;
|
|
226
|
+
if (options.expose === true) {
|
|
227
|
+
const svc = UnderpostDeploy.API.get(deployId, 'svc')[0];
|
|
228
|
+
const port = parseInt(svc[`PORT(S)`].split('/TCP')[0]);
|
|
229
|
+
logger.info(deployId, {
|
|
230
|
+
svc,
|
|
231
|
+
port,
|
|
232
|
+
});
|
|
233
|
+
shellExec(`sudo kubectl port-forward -n default svc/${svc.NAME} ${port}:${port}`, { async: true });
|
|
234
|
+
continue;
|
|
235
|
+
}
|
|
236
|
+
shellExec(`sudo kubectl delete svc ${deployId}-${env}-service`);
|
|
237
|
+
shellExec(`sudo kubectl delete deployment ${deployId}-${env}`);
|
|
238
|
+
|
|
239
|
+
const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
|
|
240
|
+
for (const host of Object.keys(confServer)) {
|
|
241
|
+
shellExec(`sudo kubectl delete HTTPProxy ${host}`);
|
|
242
|
+
if (env === 'production' && options.cert === true) shellExec(`sudo kubectl delete Certificate ${host}`);
|
|
243
|
+
if (!options.remove === true && env === 'development') concatHots += ` ${host}`;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
const manifestsPath =
|
|
247
|
+
env === 'production'
|
|
248
|
+
? `engine-private/conf/${deployId}/build/production`
|
|
249
|
+
: `manifests/deployment/${deployId}-${env}`;
|
|
250
|
+
|
|
251
|
+
if (!options.remove === true) {
|
|
252
|
+
shellExec(`sudo kubectl apply -f ./${manifestsPath}/deployment.yaml`);
|
|
253
|
+
shellExec(`sudo kubectl apply -f ./${manifestsPath}/proxy.yaml`);
|
|
254
|
+
if (env === 'production' && options.cert === true)
|
|
255
|
+
shellExec(`sudo kubectl apply -f ./${manifestsPath}/secret.yaml`);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
let renderHosts;
|
|
259
|
+
switch (process.platform) {
|
|
260
|
+
case 'linux':
|
|
261
|
+
{
|
|
262
|
+
switch (env) {
|
|
263
|
+
case 'development':
|
|
264
|
+
renderHosts = etcHost(concatHots);
|
|
265
|
+
fs.writeFileSync(`/etc/hosts`, renderHosts, 'utf8');
|
|
266
|
+
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
default:
|
|
270
|
+
break;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
break;
|
|
274
|
+
|
|
275
|
+
default:
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
if (renderHosts)
|
|
279
|
+
logger.info(
|
|
280
|
+
`
|
|
281
|
+
` + renderHosts,
|
|
282
|
+
);
|
|
283
|
+
},
|
|
284
|
+
get(deployId, kindType = 'pods') {
|
|
285
|
+
const raw = shellExec(`sudo kubectl get ${kindType} --all-namespaces -o wide`, {
|
|
286
|
+
stdout: true,
|
|
287
|
+
disableLog: true,
|
|
288
|
+
silent: true,
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
const heads = raw
|
|
292
|
+
.split(`\n`)[0]
|
|
293
|
+
.split(' ')
|
|
294
|
+
.filter((_r) => _r.trim());
|
|
295
|
+
|
|
296
|
+
const pods = raw
|
|
297
|
+
.split(`\n`)
|
|
298
|
+
.filter((r) => (deployId ? r.match(deployId) : r.trim() && !r.match('NAME')))
|
|
299
|
+
.map((r) => r.split(' ').filter((_r) => _r.trim()));
|
|
300
|
+
|
|
301
|
+
const result = [];
|
|
302
|
+
|
|
303
|
+
for (const row of pods) {
|
|
304
|
+
const pod = {};
|
|
305
|
+
let index = -1;
|
|
306
|
+
for (const head of heads) {
|
|
307
|
+
index++;
|
|
308
|
+
pod[head] = row[index];
|
|
309
|
+
}
|
|
310
|
+
result.push(pod);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return result;
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
export default UnderpostDeploy;
|
package/src/cli/env.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { getNpmRootPath, writeEnv } from '../server/conf.js';
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import { loggerFactory } from '../server/logger.js';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
|
|
6
|
+
dotenv.config();
|
|
7
|
+
|
|
8
|
+
const logger = loggerFactory(import.meta);
|
|
9
|
+
|
|
10
|
+
class UnderpostRootEnv {
|
|
11
|
+
static API = {
|
|
12
|
+
set(key, value) {
|
|
13
|
+
const exeRootPath = `${getNpmRootPath()}/underpost`;
|
|
14
|
+
const envPath = `${exeRootPath}/.env`;
|
|
15
|
+
let env = {};
|
|
16
|
+
if (fs.existsSync(envPath)) env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
17
|
+
env[key] = value;
|
|
18
|
+
writeEnv(envPath, env);
|
|
19
|
+
},
|
|
20
|
+
delete(key) {
|
|
21
|
+
const exeRootPath = `${getNpmRootPath()}/underpost`;
|
|
22
|
+
const envPath = `${exeRootPath}/.env`;
|
|
23
|
+
let env = {};
|
|
24
|
+
if (fs.existsSync(envPath)) env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
25
|
+
delete env[key];
|
|
26
|
+
writeEnv(envPath, env);
|
|
27
|
+
},
|
|
28
|
+
get(key) {
|
|
29
|
+
const exeRootPath = `${getNpmRootPath()}/underpost`;
|
|
30
|
+
const envPath = `${exeRootPath}/.env`;
|
|
31
|
+
if (!fs.existsSync(envPath)) return logger.error(`Unable to find underpost root environment`);
|
|
32
|
+
const env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
33
|
+
logger.info('underpost root', { [key]: env[key] });
|
|
34
|
+
return env[key];
|
|
35
|
+
},
|
|
36
|
+
list() {
|
|
37
|
+
const exeRootPath = `${getNpmRootPath()}/underpost`;
|
|
38
|
+
const envPath = `${exeRootPath}/.env`;
|
|
39
|
+
if (!fs.existsSync(envPath)) return logger.error(`Unable to find underpost root environment`);
|
|
40
|
+
const env = dotenv.parse(fs.readFileSync(envPath, 'utf8'));
|
|
41
|
+
logger.info('underpost root', env);
|
|
42
|
+
return env;
|
|
43
|
+
},
|
|
44
|
+
clean() {
|
|
45
|
+
const exeRootPath = `${getNpmRootPath()}/underpost`;
|
|
46
|
+
const envPath = `${exeRootPath}/.env`;
|
|
47
|
+
fs.removeSync(envPath);
|
|
48
|
+
},
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default UnderpostRootEnv;
|
package/src/cli/fs.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { v2 as cloudinary } from 'cloudinary';
|
|
2
|
+
import { loggerFactory } from '../server/logger.js';
|
|
3
|
+
import dotenv from 'dotenv';
|
|
4
|
+
import AdmZip from 'adm-zip';
|
|
5
|
+
import * as dir from 'path';
|
|
6
|
+
import fs from 'fs-extra';
|
|
7
|
+
import { Downloader } from '../server/downloader.js';
|
|
8
|
+
import UnderpostRepository from './repository.js';
|
|
9
|
+
import { shellExec } from '../server/process.js';
|
|
10
|
+
dotenv.config();
|
|
11
|
+
|
|
12
|
+
const logger = loggerFactory(import.meta);
|
|
13
|
+
|
|
14
|
+
class UnderpostFileStorage {
|
|
15
|
+
static API = {
|
|
16
|
+
cloudinaryConfig() {
|
|
17
|
+
// https://console.cloudinary.com/
|
|
18
|
+
cloudinary.config({
|
|
19
|
+
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
|
|
20
|
+
api_key: process.env.CLOUDINARY_API_KEY,
|
|
21
|
+
api_secret: process.env.CLOUDINARY_API_SECRET,
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
getStorageConf(options) {
|
|
25
|
+
let storage, storageConf;
|
|
26
|
+
if (options.deployId && typeof options.deployId === 'string') {
|
|
27
|
+
storageConf = `./engine-private/conf/${options.deployId}/storage.json`;
|
|
28
|
+
if (!fs.existsSync(storageConf)) fs.writeFileSync(storageConf, JSON.stringify({}), 'utf8');
|
|
29
|
+
storage = JSON.parse(fs.readFileSync(storageConf, 'utf8'));
|
|
30
|
+
}
|
|
31
|
+
return { storage, storageConf };
|
|
32
|
+
},
|
|
33
|
+
writeStorageConf(storage, storageConf) {
|
|
34
|
+
if (storage) fs.writeFileSync(storageConf, JSON.stringify(storage, null, 4), 'utf8');
|
|
35
|
+
},
|
|
36
|
+
async recursiveCallback(
|
|
37
|
+
path,
|
|
38
|
+
options = { rm: false, recursive: false, deployId: '', force: false, pull: false, git: false },
|
|
39
|
+
) {
|
|
40
|
+
const { storage, storageConf } = UnderpostFileStorage.API.getStorageConf(options);
|
|
41
|
+
const deleteFiles = UnderpostRepository.API.getDeleteFiles(path);
|
|
42
|
+
for (const relativePath of deleteFiles) {
|
|
43
|
+
const _path = path + '/' + relativePath;
|
|
44
|
+
if (_path in storage) {
|
|
45
|
+
await UnderpostFileStorage.API.delete(_path);
|
|
46
|
+
delete storage[_path];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const files =
|
|
50
|
+
options.git === true
|
|
51
|
+
? UnderpostRepository.API.getChangedFiles(path)
|
|
52
|
+
: await fs.readdir(path, { recursive: true });
|
|
53
|
+
if (options.pull === true) {
|
|
54
|
+
for (const _path of Object.keys(storage)) {
|
|
55
|
+
if (!fs.existsSync(_path) || options.force === true) {
|
|
56
|
+
if (options.force === true && fs.existsSync(_path)) fs.removeSync(_path);
|
|
57
|
+
await UnderpostFileStorage.API.pull(_path, options);
|
|
58
|
+
} else logger.warn(`Pull path already exists`, _path);
|
|
59
|
+
}
|
|
60
|
+
} else
|
|
61
|
+
for (const relativePath of files) {
|
|
62
|
+
const _path = path + '/' + relativePath;
|
|
63
|
+
if (fs.statSync(_path).isDirectory()) {
|
|
64
|
+
if (options.pull === true && !fs.existsSync(_path)) fs.mkdirSync(_path, { recursive: true });
|
|
65
|
+
continue;
|
|
66
|
+
} else if (!(_path in storage) || options.force === true) {
|
|
67
|
+
await UnderpostFileStorage.API.upload(_path, options);
|
|
68
|
+
if (storage) storage[_path] = {};
|
|
69
|
+
} else logger.warn('File already exists', _path);
|
|
70
|
+
}
|
|
71
|
+
UnderpostFileStorage.API.writeStorageConf(storage, storageConf);
|
|
72
|
+
if (options.git === true) {
|
|
73
|
+
shellExec(`cd ${path} && git add .`);
|
|
74
|
+
shellExec(`underpost cmt ${path} feat`);
|
|
75
|
+
}
|
|
76
|
+
},
|
|
77
|
+
async callback(
|
|
78
|
+
path,
|
|
79
|
+
options = { rm: false, recursive: false, deployId: '', force: false, pull: false, git: false },
|
|
80
|
+
) {
|
|
81
|
+
if (options.recursive === true || options.git === true)
|
|
82
|
+
return await UnderpostFileStorage.API.recursiveCallback(path, options);
|
|
83
|
+
if (options.pull === true) return await UnderpostFileStorage.API.pull(path, options);
|
|
84
|
+
if (options.rm === true) return await UnderpostFileStorage.API.delete(path, options);
|
|
85
|
+
return await UnderpostFileStorage.API.upload(path, options);
|
|
86
|
+
},
|
|
87
|
+
async upload(path, options = { rm: false, recursive: false, deployId: '', force: false, pull: false }) {
|
|
88
|
+
UnderpostFileStorage.API.cloudinaryConfig();
|
|
89
|
+
const { storage, storageConf } = UnderpostFileStorage.API.getStorageConf(options);
|
|
90
|
+
// path = UnderpostFileStorage.API.file2Zip(path);
|
|
91
|
+
const uploadResult = await cloudinary.uploader
|
|
92
|
+
.upload(path, {
|
|
93
|
+
public_id: path,
|
|
94
|
+
resource_type: 'raw',
|
|
95
|
+
overwrite: options.force === true ? true : false,
|
|
96
|
+
})
|
|
97
|
+
.catch((error) => {
|
|
98
|
+
logger.error(error, { path, stack: error.stack });
|
|
99
|
+
});
|
|
100
|
+
logger.info('upload result', uploadResult);
|
|
101
|
+
if (storage) storage[path] = {};
|
|
102
|
+
UnderpostFileStorage.API.writeStorageConf(storage, storageConf);
|
|
103
|
+
return uploadResult;
|
|
104
|
+
},
|
|
105
|
+
async pull(path) {
|
|
106
|
+
UnderpostFileStorage.API.cloudinaryConfig();
|
|
107
|
+
const folder = dir.dirname(path);
|
|
108
|
+
if (!fs.existsSync(folder)) fs.mkdirSync(folder, { recursive: true });
|
|
109
|
+
const downloadResult = await cloudinary.utils.download_archive_url({
|
|
110
|
+
public_ids: [path],
|
|
111
|
+
resource_type: 'raw',
|
|
112
|
+
});
|
|
113
|
+
logger.info('download result', downloadResult);
|
|
114
|
+
await Downloader(downloadResult, path + '.zip');
|
|
115
|
+
path = UnderpostFileStorage.API.zip2File(path + '.zip');
|
|
116
|
+
fs.removeSync(path + '.zip');
|
|
117
|
+
},
|
|
118
|
+
async delete(path) {
|
|
119
|
+
UnderpostFileStorage.API.cloudinaryConfig();
|
|
120
|
+
const deleteResult = await cloudinary.api
|
|
121
|
+
.delete_resources([path], { type: 'upload', resource_type: 'raw' })
|
|
122
|
+
.catch((error) => {
|
|
123
|
+
logger.error(error, { path, stack: error.stack });
|
|
124
|
+
});
|
|
125
|
+
logger.info('delete result', deleteResult);
|
|
126
|
+
return deleteResult;
|
|
127
|
+
},
|
|
128
|
+
file2Zip(path) {
|
|
129
|
+
const zip = new AdmZip();
|
|
130
|
+
zip.addLocalFile(path, '/');
|
|
131
|
+
path = path + '.zip';
|
|
132
|
+
zip.writeZip(path);
|
|
133
|
+
return path;
|
|
134
|
+
},
|
|
135
|
+
zip2File(path) {
|
|
136
|
+
const zip = new AdmZip(path);
|
|
137
|
+
path = path.replaceAll('.zip', '');
|
|
138
|
+
zip.extractEntryTo(
|
|
139
|
+
/*entry name*/ path.split('/').pop(),
|
|
140
|
+
/*target path*/ dir.dirname(path),
|
|
141
|
+
/*maintainEntryPath*/ false,
|
|
142
|
+
/*overwrite*/ true,
|
|
143
|
+
);
|
|
144
|
+
return path;
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export default UnderpostFileStorage;
|
package/src/cli/image.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import fs from 'fs-extra';
|
|
2
|
+
import Underpost from '../index.js';
|
|
3
|
+
import { shellCd, shellExec } from '../server/process.js';
|
|
4
|
+
import dotenv from 'dotenv';
|
|
5
|
+
import { getNpmRootPath } from '../server/conf.js';
|
|
6
|
+
import { timer } from '../client/components/core/CommonJs.js';
|
|
7
|
+
import UnderpostRootEnv from './env.js';
|
|
8
|
+
|
|
9
|
+
dotenv.config();
|
|
10
|
+
|
|
11
|
+
class UnderpostImage {
|
|
12
|
+
static API = {
|
|
13
|
+
dockerfile: {
|
|
14
|
+
pullBaseImages() {
|
|
15
|
+
shellExec(`sudo podman pull docker.io/library/debian:buster`);
|
|
16
|
+
},
|
|
17
|
+
build(
|
|
18
|
+
deployId = 'default',
|
|
19
|
+
env = 'development',
|
|
20
|
+
path = '.',
|
|
21
|
+
options = { imageArchive: false, podmanSave: false, imageName: '', imageVersion: '' },
|
|
22
|
+
) {
|
|
23
|
+
const imgName = `${
|
|
24
|
+
options.imageName && typeof options.imageName === 'string' ? options.imageName : `${deployId}-${env}`
|
|
25
|
+
}:${
|
|
26
|
+
options.imageVersion && typeof options.imageVersions === 'string' ? options.imageVersion : Underpost.version
|
|
27
|
+
}`;
|
|
28
|
+
const podManImg = `localhost/${imgName}`;
|
|
29
|
+
const imagesStoragePath = `/images`;
|
|
30
|
+
if (!fs.existsSync(`${path}${imagesStoragePath}`))
|
|
31
|
+
fs.mkdirSync(`${path}${imagesStoragePath}`, { recursive: true });
|
|
32
|
+
const tarFile = `.${imagesStoragePath}/${imgName.replace(':', '_')}.tar`;
|
|
33
|
+
|
|
34
|
+
let secrets = ' ';
|
|
35
|
+
let secretDockerInput = '';
|
|
36
|
+
|
|
37
|
+
const envObj = dotenv.parse(fs.readFileSync(`${getNpmRootPath()}/underpost/.env`, 'utf8'));
|
|
38
|
+
|
|
39
|
+
for (const key of Object.keys(envObj)) {
|
|
40
|
+
continue;
|
|
41
|
+
secrets += ` && export ${key}="${envObj[key]}" `; // $(cat gitlab-token.txt)
|
|
42
|
+
secretDockerInput += ` --secret id=${key},env=${key} \ `;
|
|
43
|
+
}
|
|
44
|
+
// --rm --no-cache
|
|
45
|
+
if (options.imageArchive !== true) {
|
|
46
|
+
fs.copyFile(`${getNpmRootPath()}/underpost/.env`, `${path}/.env.underpost`);
|
|
47
|
+
shellExec(
|
|
48
|
+
`cd ${path}${secrets}&& sudo podman build -f ./Dockerfile -t ${imgName} --pull=never --cap-add=CAP_AUDIT_WRITE${secretDockerInput}`,
|
|
49
|
+
);
|
|
50
|
+
fs.removeSync(`${path}/.env.underpost`);
|
|
51
|
+
}
|
|
52
|
+
if (options.imageArchive !== true || options.podmanSave === true)
|
|
53
|
+
shellExec(`cd ${path} && podman save -o ${tarFile} ${podManImg}`);
|
|
54
|
+
shellExec(`cd ${path} && sudo kind load image-archive ${tarFile}`);
|
|
55
|
+
},
|
|
56
|
+
async script(deployId = 'default', env = 'development', options = { run: false, build: false }) {
|
|
57
|
+
if (deployId === 'deploy') {
|
|
58
|
+
const _deployId = UnderpostRootEnv.API.get('deploy-id');
|
|
59
|
+
const _env = UnderpostRootEnv.API.get('deploy-env');
|
|
60
|
+
const _path = UnderpostRootEnv.API.get('deploy-path');
|
|
61
|
+
if (_deployId) {
|
|
62
|
+
deployId = _deployId;
|
|
63
|
+
if (_env) env = _env;
|
|
64
|
+
if (_path) path = _path;
|
|
65
|
+
} else {
|
|
66
|
+
await timer(30 * 1000);
|
|
67
|
+
return await UnderpostImage.API.script(deployId, env, path, options);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (options.build === true) {
|
|
71
|
+
const buildBasePath = `/home/dd`;
|
|
72
|
+
const repoName = `engine-${deployId.split('-')[1]}`;
|
|
73
|
+
fs.mkdirSync(buildBasePath, { recursive: true });
|
|
74
|
+
shellExec(`cd ${buildBasePath} && underpost clone underpostnet/${repoName}`);
|
|
75
|
+
shellExec(`cd ${buildBasePath} && sudo mv ./${repoName} ./engine`);
|
|
76
|
+
shellExec(`cd ${buildBasePath}/engine && underpost clone underpostnet/${repoName}-private`);
|
|
77
|
+
shellExec(`cd ${buildBasePath}/engine && sudo mv ./${repoName}-private ./engine-private`);
|
|
78
|
+
shellCd(`${buildBasePath}/engine`);
|
|
79
|
+
shellExec(`npm install`);
|
|
80
|
+
const itcScripts = fs.readdir('./engine-private/itc-scripts');
|
|
81
|
+
for (const itcScript of itcScripts)
|
|
82
|
+
if (itcScript.match(deployId)) shellExec(`node ./engine-private/itc-scripts/${itcScript}`);
|
|
83
|
+
}
|
|
84
|
+
switch (deployId) {
|
|
85
|
+
case 'dd-lampp':
|
|
86
|
+
{
|
|
87
|
+
const lamppPublicPath = '/xampp/htdocs/online';
|
|
88
|
+
shellExec(`sudo mkdir -p ${lamppPublicPath}`);
|
|
89
|
+
}
|
|
90
|
+
break;
|
|
91
|
+
|
|
92
|
+
default:
|
|
93
|
+
{
|
|
94
|
+
{
|
|
95
|
+
const originPath = `./src/db/mongo/MongooseDB.js`;
|
|
96
|
+
fs.writeFileSync(
|
|
97
|
+
originPath,
|
|
98
|
+
fs.readFileSync(originPath, 'utf8').replaceAll(
|
|
99
|
+
`connect: async (host, name) => {`,
|
|
100
|
+
`connect: async (host, name) => {
|
|
101
|
+
host = 'mongodb://mongodb-0.mongodb-service:27017';
|
|
102
|
+
`,
|
|
103
|
+
),
|
|
104
|
+
'utf8',
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
const originPath = `./src/server/valkey.js`;
|
|
110
|
+
fs.writeFileSync(
|
|
111
|
+
originPath,
|
|
112
|
+
fs.readFileSync(originPath, 'utf8').replaceAll(
|
|
113
|
+
` // port: 6379,
|
|
114
|
+
// host: 'service-valkey.default.svc.cluster.local',`,
|
|
115
|
+
` port: 6379,
|
|
116
|
+
host: 'service-valkey.default.svc.cluster.local',`,
|
|
117
|
+
),
|
|
118
|
+
'utf8',
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
shellExec(`node bin/deploy conf ${deployId} ${env}`);
|
|
125
|
+
shellExec(`node bin/deploy build-full-client ${deployId}`);
|
|
126
|
+
if (options.run === true) {
|
|
127
|
+
const runCmd = env === 'production' ? 'run prod-img' : 'run dev-img';
|
|
128
|
+
if (fs.existsSync(`./engine-private/replica`)) {
|
|
129
|
+
const replicas = await fs.readdir(`./engine-private/replica`);
|
|
130
|
+
for (const replica of replicas) {
|
|
131
|
+
shellExec(`node bin/deploy conf ${replica} ${env}`);
|
|
132
|
+
shellExec(`npm ${runCmd} ${replica} deploy`, { async: true });
|
|
133
|
+
fs.writeFileSync(`./tmp/await-deploy`, '', 'utf8');
|
|
134
|
+
const monitor = async () => {
|
|
135
|
+
await timer(1000);
|
|
136
|
+
if (fs.existsSync(`./tmp/await-deploy`)) return await monitor();
|
|
137
|
+
};
|
|
138
|
+
await monitor();
|
|
139
|
+
}
|
|
140
|
+
shellExec(`node bin/deploy conf ${deployId} ${env}`);
|
|
141
|
+
}
|
|
142
|
+
shellExec(`npm ${runCmd} ${deployId} deploy`);
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
export default UnderpostImage;
|