underpost 3.2.5 → 3.2.9
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/.github/workflows/release.cd.yml +1 -2
- package/CHANGELOG.md +351 -1
- package/CLI-HELP.md +40 -13
- package/Dockerfile +0 -4
- package/README.md +4 -4
- package/bin/build.js +14 -5
- package/bin/deploy.js +570 -1
- package/bin/file.js +6 -0
- package/conf.js +11 -2
- package/jsconfig.json +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +2 -2
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -6
- package/manifests/deployment/dd-test-development/deployment.yaml +136 -66
- package/manifests/deployment/dd-test-development/proxy.yaml +41 -5
- package/package.json +24 -15
- package/scripts/k3s-node-setup.sh +2 -2
- package/scripts/nat-iptables.sh +103 -18
- package/src/api/core/core.controller.js +10 -10
- package/src/api/core/core.service.js +10 -10
- package/src/api/default/default.controller.js +10 -10
- package/src/api/default/default.service.js +10 -10
- package/src/api/document/document.controller.js +12 -12
- package/src/api/document/document.model.js +10 -16
- package/src/api/file/file.controller.js +8 -8
- package/src/api/file/file.model.js +10 -10
- package/src/api/file/file.service.js +36 -36
- package/src/api/test/test.controller.js +8 -8
- package/src/api/test/test.service.js +8 -8
- package/src/api/user/guest.service.js +99 -0
- package/src/api/user/user.controller.js +6 -6
- package/src/api/user/user.model.js +8 -13
- package/src/api/user/user.service.js +3 -20
- package/src/cli/cluster.js +61 -14
- package/src/cli/db.js +47 -2
- package/src/cli/deploy.js +67 -35
- package/src/cli/fs.js +79 -8
- package/src/cli/image.js +43 -1
- package/src/cli/index.js +26 -1
- package/src/cli/release.js +57 -1
- package/src/cli/repository.js +69 -31
- package/src/cli/run.js +415 -36
- package/src/cli/ssh.js +1 -1
- package/src/cli/static.js +43 -115
- package/src/client/Default.index.js +21 -33
- package/src/client/components/core/404.js +4 -4
- package/src/client/components/core/500.js +4 -4
- package/src/client/components/core/Account.js +73 -60
- package/src/client/components/core/AgGrid.js +23 -33
- package/src/client/components/core/Alert.js +12 -13
- package/src/client/components/core/AppStore.js +1 -1
- package/src/client/components/core/Auth.js +35 -37
- package/src/client/components/core/Badge.js +7 -13
- package/src/client/components/core/BtnIcon.js +15 -17
- package/src/client/components/core/CalendarCore.js +42 -63
- package/src/client/components/core/Chat.js +13 -15
- package/src/client/components/core/ClientEvents.js +87 -0
- package/src/client/components/core/ColorPaletteElement.js +309 -0
- package/src/client/components/core/Content.js +17 -14
- package/src/client/components/core/Css.js +15 -71
- package/src/client/components/core/CssCore.js +12 -16
- package/src/client/components/core/D3Chart.js +4 -4
- package/src/client/components/core/Docs.js +64 -91
- package/src/client/components/core/DropDown.js +69 -91
- package/src/client/components/core/EventBus.js +92 -0
- package/src/client/components/core/EventsUI.js +14 -17
- package/src/client/components/core/FileExplorer.js +96 -228
- package/src/client/components/core/FullScreen.js +47 -75
- package/src/client/components/core/Input.js +24 -69
- package/src/client/components/core/Keyboard.js +25 -18
- package/src/client/components/core/KeyboardAvoidance.js +145 -0
- package/src/client/components/core/LoadingAnimation.js +25 -31
- package/src/client/components/core/LogIn.js +41 -41
- package/src/client/components/core/LogOut.js +23 -14
- package/src/client/components/core/Modal.js +462 -178
- package/src/client/components/core/NotificationManager.js +14 -18
- package/src/client/components/core/Panel.js +54 -50
- package/src/client/components/core/PanelForm.js +25 -125
- package/src/client/components/core/Polyhedron.js +110 -214
- package/src/client/components/core/PublicProfile.js +39 -32
- package/src/client/components/core/Recover.js +48 -44
- package/src/client/components/core/Responsive.js +88 -32
- package/src/client/components/core/RichText.js +9 -18
- package/src/client/components/core/Router.js +24 -3
- package/src/client/components/core/SearchBox.js +37 -37
- package/src/client/components/core/SignUp.js +39 -30
- package/src/client/components/core/SocketIo.js +31 -2
- package/src/client/components/core/SocketIoHandler.js +6 -6
- package/src/client/components/core/ToggleSwitch.js +8 -20
- package/src/client/components/core/ToolTip.js +5 -17
- package/src/client/components/core/Translate.js +56 -59
- package/src/client/components/core/Validator.js +26 -16
- package/src/client/components/core/Wallet.js +15 -26
- package/src/client/components/core/Worker.js +163 -27
- package/src/client/components/core/windowGetDimensions.js +7 -7
- package/src/client/components/default/{MenuDefault.js → AppShellDefault.js} +87 -87
- package/src/client/components/default/CssDefault.js +12 -12
- package/src/client/components/default/LogInDefault.js +6 -4
- package/src/client/components/default/LogOutDefault.js +6 -4
- package/src/client/components/default/RouterDefault.js +47 -0
- package/src/client/components/default/SettingsDefault.js +4 -4
- package/src/client/components/default/SignUpDefault.js +6 -4
- package/src/client/components/default/TranslateDefault.js +3 -3
- package/src/client/services/core/core.service.js +17 -49
- package/src/client/services/default/default.management.js +159 -267
- package/src/client/services/default/default.service.js +10 -16
- package/src/client/services/document/document.service.js +14 -19
- package/src/client/services/file/file.service.js +8 -13
- package/src/client/services/test/test.service.js +8 -13
- package/src/client/services/user/guest.service.js +86 -0
- package/src/client/services/user/user.management.js +5 -5
- package/src/client/services/user/user.service.js +14 -20
- package/src/client/ssr/body/404.js +3 -3
- package/src/client/ssr/body/500.js +3 -3
- package/src/client/ssr/body/CacheControl.js +5 -2
- package/src/client/ssr/body/DefaultSplashScreen.js +19 -12
- package/src/client/ssr/mailer/DefaultRecoverEmail.js +19 -20
- package/src/client/ssr/mailer/DefaultVerifyEmail.js +15 -16
- package/src/client/ssr/offline/Maintenance.js +12 -11
- package/src/client/ssr/offline/NoNetworkConnection.js +3 -3
- package/src/client/ssr/pages/Test.js +2 -2
- package/src/client/sw/core.sw.js +212 -0
- package/src/index.js +1 -1
- package/src/runtime/express/Dockerfile +4 -4
- package/src/runtime/lampp/Dockerfile +8 -7
- package/src/runtime/wp/Dockerfile +11 -17
- package/src/server/client-build-docs.js +45 -46
- package/src/server/client-build.js +334 -60
- package/src/server/client-formatted.js +47 -16
- package/src/server/conf.js +5 -4
- package/src/server/data-query.js +32 -20
- package/src/server/dns.js +22 -0
- package/src/server/ipfs-client.js +232 -91
- package/src/server/process.js +13 -27
- package/src/server/start.js +17 -3
- package/src/server/valkey.js +141 -235
- package/tsconfig.docs.json +15 -0
- package/typedoc.json +29 -0
- package/jsdoc.json +0 -52
- package/src/client/components/core/ColorPalette.js +0 -5267
- package/src/client/components/core/JoyStick.js +0 -80
- package/src/client/components/default/RoutesDefault.js +0 -49
- package/src/client/sw/default.sw.js +0 -127
- package/src/client/sw/template.sw.js +0 -84
package/src/cli/run.js
CHANGED
|
@@ -93,7 +93,6 @@ const logger = loggerFactory(import.meta);
|
|
|
93
93
|
* @property {boolean} kubeadm - Whether to run in kubeadm mode.
|
|
94
94
|
* @property {boolean} kind - Whether to run in kind mode.
|
|
95
95
|
* @property {boolean} k3s - Whether to run in k3s mode.
|
|
96
|
-
* @property {string} logType - The type of log to generate.
|
|
97
96
|
* @property {string} hosts - The hosts to use.
|
|
98
97
|
* @property {string} deployId - The deployment ID.
|
|
99
98
|
* @property {string} instanceId - The instance ID.
|
|
@@ -111,6 +110,10 @@ const logger = loggerFactory(import.meta);
|
|
|
111
110
|
* @property {string|Array<{ip: string, hostnames: string[]}>} hostAliases - Adds entries to the Pod /etc/hosts via Kubernetes hostAliases.
|
|
112
111
|
* As a string (CLI): semicolon-separated entries of "ip=hostname1,hostname2" (e.g., "127.0.0.1=foo.local,bar.local;10.1.2.3=foo.remote").
|
|
113
112
|
* As an array (programmatic): objects with `ip` and `hostnames` fields (e.g., [{ ip: "127.0.0.1", hostnames: ["foo.local"] }]).
|
|
113
|
+
* @property {boolean} gitClean - Whether to perform a `git clean` before running.
|
|
114
|
+
* @property {boolean} copy - Whether to copy the command to the clipboard instead of executing it.
|
|
115
|
+
* @property {boolean} skipFullBuild - Whether to skip the full client bundle build during deployment (supported by: sync, template-deploy).
|
|
116
|
+
* @property {boolean} pullBundle - Whether to pull the bundle before running. Use together with --skip-full-build to skip the local build entirely (supported by: sync, template-deploy).
|
|
114
117
|
* @memberof UnderpostRun
|
|
115
118
|
*/
|
|
116
119
|
const DEFAULT_OPTION = {
|
|
@@ -158,7 +161,6 @@ const DEFAULT_OPTION = {
|
|
|
158
161
|
kubeadm: false,
|
|
159
162
|
kind: false,
|
|
160
163
|
k3s: false,
|
|
161
|
-
logType: '',
|
|
162
164
|
hosts: '',
|
|
163
165
|
deployId: '',
|
|
164
166
|
instanceId: '',
|
|
@@ -176,6 +178,8 @@ const DEFAULT_OPTION = {
|
|
|
176
178
|
hostAliases: '',
|
|
177
179
|
gitClean: false,
|
|
178
180
|
copy: false,
|
|
181
|
+
skipFullBuild: false,
|
|
182
|
+
pullBundle: false,
|
|
179
183
|
};
|
|
180
184
|
|
|
181
185
|
/**
|
|
@@ -437,6 +441,15 @@ class UnderpostRun {
|
|
|
437
441
|
deployType = 'init';
|
|
438
442
|
}
|
|
439
443
|
|
|
444
|
+
// If --build is set and path is a sync-engine-* target, push the pre-built client bundle
|
|
445
|
+
// to Cloudinary so the remote container can pull it instead of rebuilding from source.
|
|
446
|
+
if (options.build && deployConfId && deployConfId.startsWith('engine-')) {
|
|
447
|
+
const confName = deployConfId.replace(/^engine-/, '');
|
|
448
|
+
const pushDeployId = options.deployId || `dd-${confName}`;
|
|
449
|
+
logger.info(`[template-deploy] Running push-bundle for deployId: ${pushDeployId}`);
|
|
450
|
+
shellExec(`${baseCommand} run push-bundle --deploy-id ${pushDeployId}`);
|
|
451
|
+
}
|
|
452
|
+
|
|
440
453
|
// Dispatch npmpkg CI workflow — this builds pwa-microservices-template first.
|
|
441
454
|
// If deployConfId is set, npmpkg.ci.yml will dispatch the engine-<conf-id> CI
|
|
442
455
|
// with sync=true after template build completes. The engine CI then dispatches
|
|
@@ -490,21 +503,6 @@ class UnderpostRun {
|
|
|
490
503
|
: await Underpost.release.pwa(sanitizedMessage, options);
|
|
491
504
|
pbcopy(triggerCmd + ' && cd /home/dd/engine');
|
|
492
505
|
},
|
|
493
|
-
/**
|
|
494
|
-
* @method template-deploy-image
|
|
495
|
-
* @description Dispatches the Docker image CI workflow for the `engine` repository.
|
|
496
|
-
* @param {string} path - The input value, identifier, or path for the operation.
|
|
497
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
498
|
-
* @memberof UnderpostRun
|
|
499
|
-
*/
|
|
500
|
-
'template-deploy-image': (path, options = DEFAULT_OPTION) => {
|
|
501
|
-
Underpost.repo.dispatchWorkflow({
|
|
502
|
-
repo: `${process.env.GITHUB_USERNAME}/engine`,
|
|
503
|
-
workflowFile: 'docker-image.ci.yml',
|
|
504
|
-
ref: 'master',
|
|
505
|
-
inputs: {},
|
|
506
|
-
});
|
|
507
|
-
},
|
|
508
506
|
/**
|
|
509
507
|
* @method docker-image
|
|
510
508
|
* @description Dispatches the Docker image CI workflow (`docker-image.ci.yml`) for the `engine` repository via `workflow_dispatch`.
|
|
@@ -515,7 +513,7 @@ class UnderpostRun {
|
|
|
515
513
|
'docker-image': (path, options = DEFAULT_OPTION) => {
|
|
516
514
|
Underpost.repo.dispatchWorkflow({
|
|
517
515
|
repo: `${process.env.GITHUB_USERNAME}/engine`,
|
|
518
|
-
workflowFile:
|
|
516
|
+
workflowFile: `docker-image${path ? `.${path}` : ''}.ci.yml`,
|
|
519
517
|
ref: 'master',
|
|
520
518
|
inputs: {},
|
|
521
519
|
});
|
|
@@ -652,7 +650,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
652
650
|
sync: async (path, options = DEFAULT_OPTION) => {
|
|
653
651
|
// Dev usage: node bin run --dev --build sync dd-default
|
|
654
652
|
const env = options.dev ? 'development' : 'production';
|
|
655
|
-
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
653
|
+
const baseCommand = 'node bin'; // options.dev ? 'node bin' : 'underpost';
|
|
656
654
|
const baseClusterCommand = options.dev ? ' --dev' : '';
|
|
657
655
|
const clusterFlag = options.k3s ? ' --k3s' : options.kind ? ' --kind' : ' --kubeadm';
|
|
658
656
|
const defaultPath = [
|
|
@@ -669,6 +667,15 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
669
667
|
image = image ? image : defaultPath[3];
|
|
670
668
|
node = node ? node : defaultPath[4];
|
|
671
669
|
shellExec(`${baseCommand} cluster --ns-use ${options.namespace}`);
|
|
670
|
+
|
|
671
|
+
if (image && !image.startsWith('localhost'))
|
|
672
|
+
Underpost.image.pullDockerHubImage({
|
|
673
|
+
dockerhubImage: image,
|
|
674
|
+
kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
|
|
675
|
+
kubeadm: options.nodeName || options.kubeadm,
|
|
676
|
+
k3s: options.k3s,
|
|
677
|
+
});
|
|
678
|
+
|
|
672
679
|
if (isDeployRunnerContext(path, options)) {
|
|
673
680
|
if (!options.disablePrivateConfUpdate) {
|
|
674
681
|
const { validVersion } = Underpost.repo.privateConfUpdate(deployId);
|
|
@@ -691,12 +698,15 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
691
698
|
: '';
|
|
692
699
|
const gitCleanFlag = options.gitClean ? ' --git-clean' : '';
|
|
693
700
|
|
|
701
|
+
const skipFullBuildFlag = options.skipFullBuild ? ' --skip-full-build' : '';
|
|
702
|
+
const pullBundleFlag = options.pullBundle ? ' --pull-bundle' : '';
|
|
703
|
+
|
|
694
704
|
shellExec(
|
|
695
705
|
`${baseCommand} deploy${clusterFlag} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
|
|
696
706
|
image ? ` --image ${image}` : ''
|
|
697
707
|
}${versions ? ` --versions ${versions}` : ''}${
|
|
698
708
|
options.namespace ? ` --namespace ${options.namespace}` : ''
|
|
699
|
-
}${timeoutFlags}${cmdString}${gitCleanFlag} ${deployId} ${env}`,
|
|
709
|
+
}${timeoutFlags}${cmdString}${gitCleanFlag}${skipFullBuildFlag}${pullBundleFlag} ${deployId} ${env}`,
|
|
700
710
|
);
|
|
701
711
|
|
|
702
712
|
if (isDeployRunnerContext(path, options)) {
|
|
@@ -928,12 +938,17 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
928
938
|
image: _image,
|
|
929
939
|
fromPort: _fromPort,
|
|
930
940
|
toPort: _toPort,
|
|
941
|
+
fromDebugPort: _fromDebugPort,
|
|
942
|
+
toDebugPort: _toDebugPort,
|
|
931
943
|
cmd: _cmd,
|
|
932
944
|
volumes: _volumes,
|
|
933
945
|
metadata: _metadata,
|
|
934
946
|
} = instance;
|
|
935
947
|
if (id !== _id) continue;
|
|
936
948
|
const _deployId = `${deployId}-${_id}`;
|
|
949
|
+
// Use debug ports in development when defined, fall back to production ports.
|
|
950
|
+
if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
|
|
951
|
+
if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
|
|
937
952
|
const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
|
|
938
953
|
hostTest: _host,
|
|
939
954
|
namespace: options.namespace,
|
|
@@ -1003,12 +1018,17 @@ EOF
|
|
|
1003
1018
|
image: _image,
|
|
1004
1019
|
fromPort: _fromPort,
|
|
1005
1020
|
toPort: _toPort,
|
|
1021
|
+
fromDebugPort: _fromDebugPort,
|
|
1022
|
+
toDebugPort: _toDebugPort,
|
|
1006
1023
|
cmd: _cmd,
|
|
1007
1024
|
volumes: _volumes,
|
|
1008
1025
|
metadata: _metadata,
|
|
1009
1026
|
} = instance;
|
|
1010
1027
|
if (id !== _id) continue;
|
|
1011
1028
|
const _deployId = `${deployId}-${_id}`;
|
|
1029
|
+
// Use debug ports in development when defined, fall back to production ports.
|
|
1030
|
+
if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
|
|
1031
|
+
if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
|
|
1012
1032
|
etcHosts.push(_host);
|
|
1013
1033
|
if (options.expose) continue;
|
|
1014
1034
|
// Examples images:
|
|
@@ -1016,12 +1036,13 @@ EOF
|
|
|
1016
1036
|
// `localhost/rockylinux9-underpost:${Underpost.version}`
|
|
1017
1037
|
if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
|
|
1018
1038
|
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1039
|
+
if (_image && !_image.startsWith('localhost'))
|
|
1040
|
+
Underpost.image.pullDockerHubImage({
|
|
1041
|
+
dockerhubImage: _image,
|
|
1042
|
+
kind: options.kind || (!options.nodeName && !options.kubeadm && !options.k3s),
|
|
1043
|
+
kubeadm: options.nodeName || options.kubeadm,
|
|
1044
|
+
k3s: options.k3s,
|
|
1045
|
+
});
|
|
1025
1046
|
|
|
1026
1047
|
const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
|
|
1027
1048
|
hostTest: _host,
|
|
@@ -1095,7 +1116,6 @@ EOF
|
|
|
1095
1116
|
targetTraffic,
|
|
1096
1117
|
ignorePods,
|
|
1097
1118
|
options.namespace,
|
|
1098
|
-
options.logType,
|
|
1099
1119
|
);
|
|
1100
1120
|
|
|
1101
1121
|
if (!ready) {
|
|
@@ -1115,6 +1135,153 @@ EOF
|
|
|
1115
1135
|
}
|
|
1116
1136
|
},
|
|
1117
1137
|
|
|
1138
|
+
/**
|
|
1139
|
+
* @method instance-build-manifest
|
|
1140
|
+
* @description Builds a Kubernetes Deployment + Service manifest for a specific instance entry
|
|
1141
|
+
* from `conf.instances.json` and writes it to a file.
|
|
1142
|
+
* Traffic colour is automatically chosen as the opposite of the current live colour (blue/green),
|
|
1143
|
+
* defaulting to `blue` when no deployment is running yet.
|
|
1144
|
+
*
|
|
1145
|
+
* If `--build` is supplied the image is built from the project Dockerfile and loaded into the
|
|
1146
|
+
* cluster before the manifest is written (kind by default; `--kubeadm` / `--k3s` override).
|
|
1147
|
+
*
|
|
1148
|
+
* @param {string} path - Comma-separated: `deployId,instanceId[,projectPath]`.
|
|
1149
|
+
* `projectPath` is the root directory that contains the `Dockerfile` (e.g. `./cyberia-client`).
|
|
1150
|
+
* Artifacts are written to `<projectPath>/manifests/<env>/Dockerfile` and
|
|
1151
|
+
* `<projectPath>/manifests/<env>/deployment.yaml`.
|
|
1152
|
+
* In production, files are also copied to `<projectPath>/Dockerfile` and
|
|
1153
|
+
* `<projectPath>/deployment.yaml`.
|
|
1154
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1155
|
+
* @memberof UnderpostRun
|
|
1156
|
+
*/
|
|
1157
|
+
'instance-build-manifest': (path, options = DEFAULT_OPTION) => {
|
|
1158
|
+
const env = options.dev ? 'development' : 'production';
|
|
1159
|
+
let [deployId, id, projectPath] = path.split(',');
|
|
1160
|
+
const rootPath = projectPath ? projectPath : '.';
|
|
1161
|
+
const envManifestPath = `${rootPath}/manifests/deployments/${id}-${env}`;
|
|
1162
|
+
const outputPath = `${envManifestPath}/deployment.yaml`;
|
|
1163
|
+
const dockerfileManifestPath = `${envManifestPath}/Dockerfile`;
|
|
1164
|
+
|
|
1165
|
+
fs.mkdirpSync(envManifestPath);
|
|
1166
|
+
|
|
1167
|
+
const confInstances = JSON.parse(
|
|
1168
|
+
fs.readFileSync(`./engine-private/conf/${deployId}/conf.instances.json`, 'utf8'),
|
|
1169
|
+
);
|
|
1170
|
+
|
|
1171
|
+
const instance = confInstances.find((i) => i.id === id);
|
|
1172
|
+
if (!instance) {
|
|
1173
|
+
logger.error(`Instance with id '${id}' not found in conf.instances.json for deployId '${deployId}'`);
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
|
|
1177
|
+
let {
|
|
1178
|
+
id: _id,
|
|
1179
|
+
host: _host,
|
|
1180
|
+
path: _path,
|
|
1181
|
+
image: _image,
|
|
1182
|
+
fromPort: _fromPort,
|
|
1183
|
+
toPort: _toPort,
|
|
1184
|
+
fromDebugPort: _fromDebugPort,
|
|
1185
|
+
toDebugPort: _toDebugPort,
|
|
1186
|
+
cmd: _cmd,
|
|
1187
|
+
volumes: _volumes,
|
|
1188
|
+
metadata: _metadata,
|
|
1189
|
+
runtime: _runtime,
|
|
1190
|
+
} = instance;
|
|
1191
|
+
|
|
1192
|
+
// Resolve Dockerfile source: use runtime-specific path when instance defines a runtime.
|
|
1193
|
+
const dockerfileSourcePath = _runtime ? `src/runtime/${_runtime}/Dockerfile` : `${rootPath}/Dockerfile`;
|
|
1194
|
+
if (fs.existsSync(dockerfileSourcePath)) {
|
|
1195
|
+
fs.copyFileSync(dockerfileSourcePath, dockerfileManifestPath);
|
|
1196
|
+
} else {
|
|
1197
|
+
logger.warn(`[instance-build-manifest] Dockerfile not found at ${dockerfileSourcePath}`);
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
const _deployId = `${deployId}-${_id}`;
|
|
1201
|
+
if (!_image) _image = `underpost/underpost-engine:${Underpost.version}`;
|
|
1202
|
+
// Use debug ports in development when defined, fall back to production ports.
|
|
1203
|
+
if (env === 'development' && _fromDebugPort) _fromPort = _fromDebugPort;
|
|
1204
|
+
if (env === 'development' && _toDebugPort) _toPort = _toDebugPort;
|
|
1205
|
+
|
|
1206
|
+
// Build image from projectPath Dockerfile and load into cluster when --build is set.
|
|
1207
|
+
if (options.build && projectPath) {
|
|
1208
|
+
const isKind = !options.kubeadm && !options.k3s;
|
|
1209
|
+
Underpost.image.build({
|
|
1210
|
+
path: projectPath,
|
|
1211
|
+
imageName: _image,
|
|
1212
|
+
podmanSave: true,
|
|
1213
|
+
imagePath: projectPath,
|
|
1214
|
+
kind: isKind,
|
|
1215
|
+
kubeadm: !!options.kubeadm,
|
|
1216
|
+
k3s: !!options.k3s,
|
|
1217
|
+
reset: !!options.reset,
|
|
1218
|
+
dev: options.dev,
|
|
1219
|
+
});
|
|
1220
|
+
logger.info(`[instance-build-manifest] Image built and loaded`, {
|
|
1221
|
+
image: _image,
|
|
1222
|
+
cluster: isKind ? 'kind' : options.kubeadm ? 'kubeadm' : 'k3s',
|
|
1223
|
+
});
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1226
|
+
// Determine target traffic: opposite of current, or 'blue' if nothing is running yet.
|
|
1227
|
+
const currentTraffic = Underpost.deploy.getCurrentTraffic(_deployId, {
|
|
1228
|
+
hostTest: _host,
|
|
1229
|
+
namespace: options.namespace,
|
|
1230
|
+
});
|
|
1231
|
+
const targetTraffic = currentTraffic ? (currentTraffic === 'blue' ? 'green' : 'blue') : 'blue';
|
|
1232
|
+
|
|
1233
|
+
// Resolve {{grpc-service-dns}} using the parent deploy's current (or default) traffic.
|
|
1234
|
+
const parentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace }) || 'blue';
|
|
1235
|
+
const resolvedCmd = _cmd[env].map((c) =>
|
|
1236
|
+
c.replaceAll(
|
|
1237
|
+
'{{grpc-service-dns}}',
|
|
1238
|
+
`${deployId}-grpc-service-${env}-${parentTraffic}.${options.namespace || 'default'}.svc.cluster.local:50051`,
|
|
1239
|
+
),
|
|
1240
|
+
);
|
|
1241
|
+
|
|
1242
|
+
const deploymentYaml =
|
|
1243
|
+
`---\n` +
|
|
1244
|
+
Underpost.deploy
|
|
1245
|
+
.deploymentYamlPartsFactory({
|
|
1246
|
+
deployId: _deployId,
|
|
1247
|
+
env,
|
|
1248
|
+
suffix: targetTraffic,
|
|
1249
|
+
resources: Underpost.deploy.resourcesFactory(options),
|
|
1250
|
+
replicas: options.replicas,
|
|
1251
|
+
image: _image,
|
|
1252
|
+
namespace: options.namespace,
|
|
1253
|
+
volumes: _volumes,
|
|
1254
|
+
cmd: resolvedCmd,
|
|
1255
|
+
})
|
|
1256
|
+
.replace('{{ports}}', buildKindPorts(_fromPort, _toPort));
|
|
1257
|
+
|
|
1258
|
+
fs.writeFileSync(outputPath, deploymentYaml, 'utf8');
|
|
1259
|
+
logger.info(`[instance-build-manifest] Manifest written to ${outputPath}`, {
|
|
1260
|
+
deployId: _deployId,
|
|
1261
|
+
env,
|
|
1262
|
+
traffic: targetTraffic,
|
|
1263
|
+
image: _image,
|
|
1264
|
+
});
|
|
1265
|
+
|
|
1266
|
+
if (env === 'production') {
|
|
1267
|
+
if (fs.existsSync(dockerfileManifestPath)) {
|
|
1268
|
+
fs.copyFileSync(dockerfileManifestPath, `${rootPath}/Dockerfile`);
|
|
1269
|
+
}
|
|
1270
|
+
fs.copyFileSync(outputPath, `${rootPath}/deployment.yaml`);
|
|
1271
|
+
logger.info('[instance-build-manifest] Production artifacts copied to project root', {
|
|
1272
|
+
rootPath,
|
|
1273
|
+
dockerfile: `${rootPath}/Dockerfile`,
|
|
1274
|
+
deployment: `${rootPath}/deployment.yaml`,
|
|
1275
|
+
});
|
|
1276
|
+
const ciSrc = `./.github/workflows/docker-image.${_runtime}.ci.yml`;
|
|
1277
|
+
if (fs.existsSync(ciSrc)) {
|
|
1278
|
+
if (!fs.existsSync(`${rootPath}/.github/workflows`)) fs.mkdirpSync(`${rootPath}/.github/workflows`);
|
|
1279
|
+
fs.copyFileSync(ciSrc, `${rootPath}/.github/workflows/docker-image.${_runtime}.ci.yml`);
|
|
1280
|
+
logger.info(`[instance-build-manifest] CI workflow copied`, { src: ciSrc });
|
|
1281
|
+
}
|
|
1282
|
+
}
|
|
1283
|
+
},
|
|
1284
|
+
|
|
1118
1285
|
/**
|
|
1119
1286
|
* @method ls-deployments
|
|
1120
1287
|
* @description Retrieves and logs a table of Kubernetes deployments using `Underpost.deploy.get`.
|
|
@@ -1139,6 +1306,44 @@ EOF
|
|
|
1139
1306
|
shellExec(`${options.underpostRoot}/scripts/rocky-setup.sh${options.dev ? ' --install-dev' : ``}`);
|
|
1140
1307
|
},
|
|
1141
1308
|
|
|
1309
|
+
/**
|
|
1310
|
+
* @method install-crio
|
|
1311
|
+
* @description Installs and configures CRI-O as the container runtime for kubeadm clusters.
|
|
1312
|
+
* Adds the stable v1.33 CRI-O yum repository, installs the `cri-o` package, configures
|
|
1313
|
+
* the systemd cgroup driver, enables the `crio` service, and writes `/etc/crictl.yaml`
|
|
1314
|
+
* so that `crictl` targets the CRI-O socket by default.
|
|
1315
|
+
* @param {string} path - Unused.
|
|
1316
|
+
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
1317
|
+
* @memberof UnderpostRun
|
|
1318
|
+
*/
|
|
1319
|
+
'install-crio': (path, options = DEFAULT_OPTION) => {
|
|
1320
|
+
logger.info('Installing CRI-O...');
|
|
1321
|
+
shellExec(`cat <<EOF | sudo tee /etc/yum.repos.d/cri-o.repo
|
|
1322
|
+
[cri-o]
|
|
1323
|
+
name=CRI-O
|
|
1324
|
+
baseurl=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/v1.33/rpm/
|
|
1325
|
+
enabled=1
|
|
1326
|
+
gpgcheck=1
|
|
1327
|
+
gpgkey=https://download.opensuse.org/repositories/isv:/cri-o:/stable:/v1.33/rpm/repodata/repomd.xml.key
|
|
1328
|
+
EOF`);
|
|
1329
|
+
shellExec(`sudo dnf -y install cri-o`);
|
|
1330
|
+
// crictl is in the kubernetes repo but excluded by default — install it explicitly
|
|
1331
|
+
shellExec(`sudo yum install -y cri-tools --disableexcludes=kubernetes`);
|
|
1332
|
+
// Ensure CRI-O uses systemd cgroup driver (matches kubelet default)
|
|
1333
|
+
shellExec(
|
|
1334
|
+
`sudo sed -i 's/^#\?cgroup_manager =.*/cgroup_manager = "systemd"/' /etc/crio/crio.conf 2>/dev/null || true`,
|
|
1335
|
+
);
|
|
1336
|
+
shellExec(`sudo systemctl enable --now crio`);
|
|
1337
|
+
logger.info('CRI-O installed and started.');
|
|
1338
|
+
// Write crictl config so all crictl calls default to the CRI-O socket
|
|
1339
|
+
shellExec(`cat <<EOF | sudo tee /etc/crictl.yaml
|
|
1340
|
+
runtime-endpoint: unix:///var/run/crio/crio.sock
|
|
1341
|
+
image-endpoint: unix:///var/run/crio/crio.sock
|
|
1342
|
+
timeout: 10
|
|
1343
|
+
debug: false
|
|
1344
|
+
EOF`);
|
|
1345
|
+
},
|
|
1346
|
+
|
|
1142
1347
|
/**
|
|
1143
1348
|
* @method dd-container
|
|
1144
1349
|
* @description Deploys a development or debug container tasks jobs, setting up necessary volumes and images, and running specified commands within the container.
|
|
@@ -1281,6 +1486,9 @@ EOF
|
|
|
1281
1486
|
/**
|
|
1282
1487
|
* @method promote
|
|
1283
1488
|
* @description Switches traffic between blue/green deployments for a specified deployment ID(s) (uses `dd.router` for 'dd', or a specific ID).
|
|
1489
|
+
* When `--tls` is set, rebuilds the proxy manifest with `--cert` so the HTTPProxy includes
|
|
1490
|
+
* TLS config, deletes stale Certificate resources, then reapplies the proxy and secret.yaml
|
|
1491
|
+
* (cert-manager Certificate resources) for each affected deployment.
|
|
1284
1492
|
* @param {string} path - The input value, identifier, or path for the operation (used as a comma-separated string: `deployId,env,replicas`).
|
|
1285
1493
|
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1286
1494
|
* @memberof UnderpostRun
|
|
@@ -1289,11 +1497,34 @@ EOF
|
|
|
1289
1497
|
let [inputDeployId, inputEnv, inputReplicas] = path.split(',');
|
|
1290
1498
|
if (!inputEnv) inputEnv = 'production';
|
|
1291
1499
|
if (!inputReplicas) inputReplicas = 1;
|
|
1500
|
+
// TODO: normalize: --tls maps to --cert for deploy.js isValidTLSContext compatibility
|
|
1501
|
+
if (options.tls) options.cert = true;
|
|
1502
|
+
|
|
1503
|
+
const applyCerts = (deployId, targetTraffic) => {
|
|
1504
|
+
if (!options.tls) return;
|
|
1505
|
+
// Rebuild proxy.yaml with --cert so the HTTPProxy includes TLS virtualhost config
|
|
1506
|
+
shellExec(
|
|
1507
|
+
`node bin deploy --build-manifest --cert --traffic ${targetTraffic} --replicas ${inputReplicas} --namespace ${options.namespace} ${deployId} ${inputEnv}`,
|
|
1508
|
+
);
|
|
1509
|
+
// Delete stale Certificate resources before reapplying
|
|
1510
|
+
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
1511
|
+
if (fs.existsSync(confServerPath)) {
|
|
1512
|
+
for (const host of Object.keys(JSON.parse(fs.readFileSync(confServerPath, 'utf8'))))
|
|
1513
|
+
shellExec(`sudo kubectl delete Certificate ${host} -n ${options.namespace} --ignore-not-found`);
|
|
1514
|
+
}
|
|
1515
|
+
shellExec(
|
|
1516
|
+
`sudo kubectl apply -f ./engine-private/conf/${deployId}/build/${inputEnv}/proxy.yaml -n ${options.namespace}`,
|
|
1517
|
+
);
|
|
1518
|
+
const secretPath = `./engine-private/conf/${deployId}/build/${inputEnv}/secret.yaml`;
|
|
1519
|
+
if (fs.existsSync(secretPath)) shellExec(`kubectl apply -f ${secretPath} -n ${options.namespace}`);
|
|
1520
|
+
};
|
|
1521
|
+
|
|
1292
1522
|
if (inputDeployId === 'dd') {
|
|
1293
1523
|
for (const deployId of fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8').split(',')) {
|
|
1294
1524
|
const currentTraffic = Underpost.deploy.getCurrentTraffic(deployId, { namespace: options.namespace });
|
|
1295
1525
|
const targetTraffic = currentTraffic === 'blue' ? 'green' : 'blue';
|
|
1296
1526
|
Underpost.deploy.switchTraffic(deployId, inputEnv, targetTraffic, inputReplicas, options.namespace, options);
|
|
1527
|
+
applyCerts(deployId, targetTraffic);
|
|
1297
1528
|
}
|
|
1298
1529
|
} else {
|
|
1299
1530
|
const currentTraffic = Underpost.deploy.getCurrentTraffic(inputDeployId, { namespace: options.namespace });
|
|
@@ -1306,6 +1537,7 @@ EOF
|
|
|
1306
1537
|
options.namespace,
|
|
1307
1538
|
options,
|
|
1308
1539
|
);
|
|
1540
|
+
applyCerts(inputDeployId, targetTraffic);
|
|
1309
1541
|
}
|
|
1310
1542
|
},
|
|
1311
1543
|
/**
|
|
@@ -1920,6 +2152,16 @@ EOF
|
|
|
1920
2152
|
|
|
1921
2153
|
shellCd('/home/dd/engine');
|
|
1922
2154
|
},
|
|
2155
|
+
/**
|
|
2156
|
+
* @method pull-rocky-image
|
|
2157
|
+
* @description Pulls the base `rockylinux:9` image from Docker Hub via Podman.
|
|
2158
|
+
* @param {string} path - The input value, identifier, or path for the operation.
|
|
2159
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
2160
|
+
* @memberof UnderpostRun
|
|
2161
|
+
*/
|
|
2162
|
+
'pull-rocky-image': (path, options = DEFAULT_OPTION) => {
|
|
2163
|
+
shellExec(`sudo podman pull docker.io/library/rockylinux:9`);
|
|
2164
|
+
},
|
|
1923
2165
|
/**
|
|
1924
2166
|
* @method rmi
|
|
1925
2167
|
* @description Forces the removal of all local Podman images (`podman rmi $(podman images -qa) --force`).
|
|
@@ -1949,13 +2191,6 @@ EOF
|
|
|
1949
2191
|
} else shellExec(`sudo kill -9 $(lsof -t -i:${_path})`);
|
|
1950
2192
|
}
|
|
1951
2193
|
},
|
|
1952
|
-
/**
|
|
1953
|
-
* @method secret
|
|
1954
|
-
* @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
|
|
1955
|
-
* @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
|
|
1956
|
-
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
1957
|
-
* @memberof UnderpostRun
|
|
1958
|
-
*/
|
|
1959
2194
|
/**
|
|
1960
2195
|
* @method generate-pass
|
|
1961
2196
|
* @description Generates a cryptographically secure random password that satisfies all validatePassword
|
|
@@ -1992,10 +2227,16 @@ EOF
|
|
|
1992
2227
|
if (options.copy) pbcopy(password);
|
|
1993
2228
|
else console.log(password);
|
|
1994
2229
|
},
|
|
1995
|
-
|
|
2230
|
+
/**
|
|
2231
|
+
* @method secret
|
|
2232
|
+
* @description Creates an Underpost secret named 'underpost' from a file, defaulting to `/home/dd/engine/engine-private/conf/dd-cron/.env.production` if no path is provided.
|
|
2233
|
+
* @param {string} path - The input value, identifier, or path for the operation (used as the optional path to the secret file).
|
|
2234
|
+
* @param {Object} options - The default underpost runner options for customizing workflow
|
|
2235
|
+
* @memberof UnderpostRun
|
|
2236
|
+
*/
|
|
1996
2237
|
secret: (path, options = DEFAULT_OPTION) => {
|
|
1997
2238
|
const secretPath = path ? path : `/home/dd/engine/engine-private/conf/dd-cron/.env.production`;
|
|
1998
|
-
const command =
|
|
2239
|
+
const command = `${options.dev ? 'node bin' : 'underpost'} secret underpost --create-from-file ${secretPath}`;
|
|
1999
2240
|
shellExec(command);
|
|
2000
2241
|
},
|
|
2001
2242
|
/**
|
|
@@ -2166,6 +2407,144 @@ EOF`;
|
|
|
2166
2407
|
if (options.logs) shellExec(`kubectl logs -f ${podName} -n ${namespace}`, { async: true });
|
|
2167
2408
|
}
|
|
2168
2409
|
},
|
|
2410
|
+
|
|
2411
|
+
/**
|
|
2412
|
+
* @method push-bundle
|
|
2413
|
+
* @description Builds the client zip for the specified deployment, splits it into parts, and uploads to file storage.
|
|
2414
|
+
* Steps: set env, build+split zip, switch to cron env, upload parts to storage.
|
|
2415
|
+
* @param {string} path - Optional `fsPath.splitOption` string.
|
|
2416
|
+
* Examples: `build` (default split 8), `build.16` (split 16 MB), `build.none-split` (no split flag).
|
|
2417
|
+
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
2418
|
+
* @param {string} [options.deployId] - Override deploy ID.
|
|
2419
|
+
* @param {boolean} [options.dev] - Use development environment; defaults to production.
|
|
2420
|
+
* @memberof UnderpostRun
|
|
2421
|
+
*/
|
|
2422
|
+
'push-bundle': (path = '', options = DEFAULT_OPTION) => {
|
|
2423
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2424
|
+
const env = options.dev ? 'development' : 'production';
|
|
2425
|
+
const deployId = options.deployId || 'dd-default';
|
|
2426
|
+
const pathParts = (path || '').split('.');
|
|
2427
|
+
const fsPath = (pathParts[0] || '').trim() || 'build';
|
|
2428
|
+
const splitOption = (pathParts[1] || '').trim();
|
|
2429
|
+
|
|
2430
|
+
let splitFlag = '--split 8';
|
|
2431
|
+
if (splitOption) {
|
|
2432
|
+
if (splitOption === 'none-split') {
|
|
2433
|
+
splitFlag = '';
|
|
2434
|
+
} else {
|
|
2435
|
+
const splitMb = Number(splitOption);
|
|
2436
|
+
if (Number.isFinite(splitMb) && splitMb > 0) {
|
|
2437
|
+
splitFlag = `--split ${splitMb}`;
|
|
2438
|
+
} else {
|
|
2439
|
+
logger.warn('push-bundle: invalid split option, using default split 8', {
|
|
2440
|
+
path,
|
|
2441
|
+
splitOption,
|
|
2442
|
+
});
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
}
|
|
2446
|
+
|
|
2447
|
+
shellExec(`${baseCommand} env ${deployId} ${env}`);
|
|
2448
|
+
shellExec(`${baseCommand} client ${deployId} --build-zip${splitFlag ? ` ${splitFlag}` : ''}`);
|
|
2449
|
+
shellExec(
|
|
2450
|
+
`${baseCommand} fs ${fsPath} --recursive --deploy-id ${deployId} --storage-file-path engine-private/conf/${deployId}/storage.bundle.json --force`,
|
|
2451
|
+
);
|
|
2452
|
+
},
|
|
2453
|
+
|
|
2454
|
+
/**
|
|
2455
|
+
* @method pull-bundle
|
|
2456
|
+
* @description Downloads split zip parts from file storage, merges and extracts them, and moves the result into the public directory.
|
|
2457
|
+
* Steps: set env, download parts (omit-unzip), merge zip, unzip, remove zip + parts, move to public/<host>[/path].
|
|
2458
|
+
* Iterates over every non-singleReplica, non-redirect, non-disabledRebuild route in conf.server.json
|
|
2459
|
+
* so that multi-path deployments are handled correctly.
|
|
2460
|
+
* @param {string} path - Optional comma-separated host name(s) to restrict processing (e.g. 'underpost.net' or 'a.com,b.com').
|
|
2461
|
+
* If omitted, all hosts from `engine-private/conf/<deployId>/conf.server.json` are used.
|
|
2462
|
+
* @param {Object} options - The default underpost runner options for customizing workflow.
|
|
2463
|
+
* @param {string} [options.deployId] - Deploy ID for storage lookup (defaults to 'dd-default').
|
|
2464
|
+
* @param {boolean} [options.dev] - Use development environment; defaults to production.
|
|
2465
|
+
* @memberof UnderpostRun
|
|
2466
|
+
*/
|
|
2467
|
+
'pull-bundle': (path = '', options = DEFAULT_OPTION) => {
|
|
2468
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
2469
|
+
const env = options.dev ? 'development' : 'production';
|
|
2470
|
+
const deployId = options.deployId || 'dd-default';
|
|
2471
|
+
const confServerPath = `./engine-private/conf/${deployId}/conf.server.json`;
|
|
2472
|
+
const confServer = fs.existsSync(confServerPath) ? loadConfServerJson(confServerPath) : {};
|
|
2473
|
+
const hostsArg = path
|
|
2474
|
+
? path
|
|
2475
|
+
.split(',')
|
|
2476
|
+
.map((h) => h.trim())
|
|
2477
|
+
.filter(Boolean)
|
|
2478
|
+
: Object.keys(confServer);
|
|
2479
|
+
|
|
2480
|
+
if (hostsArg.length === 0) {
|
|
2481
|
+
logger.error('pull-bundle: no hosts resolved', { deployId, path, confServerPath });
|
|
2482
|
+
return;
|
|
2483
|
+
}
|
|
2484
|
+
|
|
2485
|
+
shellExec(`${baseCommand} env ${deployId} ${env}`);
|
|
2486
|
+
if (!fs.existsSync('./build')) fs.mkdirSync('./build', { recursive: true });
|
|
2487
|
+
shellExec(
|
|
2488
|
+
`${baseCommand} fs build --recursive --deploy-id ${deployId} --storage-file-path engine-private/conf/${deployId}/storage.bundle.json --pull --omit-unzip`,
|
|
2489
|
+
);
|
|
2490
|
+
|
|
2491
|
+
for (const host of hostsArg) {
|
|
2492
|
+
// Gather all routes for this host; fall back to root '/' when host is not in confServer
|
|
2493
|
+
// (e.g. when hosts were provided explicitly via the path argument).
|
|
2494
|
+
const routePaths = confServer[host] ? Object.keys(confServer[host]) : ['/'];
|
|
2495
|
+
|
|
2496
|
+
for (const routePath of routePaths) {
|
|
2497
|
+
const routeConf = confServer[host] ? confServer[host][routePath] || {} : {};
|
|
2498
|
+
// Skip routes that are not built by buildClient (mirrors buildClient skip conditions)
|
|
2499
|
+
if (routeConf.singleReplica || routeConf.redirect || routeConf.disabledRebuild) continue;
|
|
2500
|
+
|
|
2501
|
+
// buildClient names the zip as "<host>-<path-no-slashes>.zip"
|
|
2502
|
+
// e.g. host="underpost.net", path="/" → buildId="underpost.net-", zip="build/underpost.net-.zip"
|
|
2503
|
+
// e.g. host="app.net", path="/admin" → buildId="app.net-admin", zip="build/app.net-admin.zip"
|
|
2504
|
+
const buildId = `${host}-${routePath.replaceAll('/', '')}`;
|
|
2505
|
+
const zipPath = `build/${buildId}.zip`;
|
|
2506
|
+
const buildDir = './build';
|
|
2507
|
+
const hasZip = fs.existsSync(zipPath);
|
|
2508
|
+
const hasParts =
|
|
2509
|
+
fs.existsSync(buildDir) &&
|
|
2510
|
+
fs
|
|
2511
|
+
.readdirSync(buildDir)
|
|
2512
|
+
.some((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`));
|
|
2513
|
+
|
|
2514
|
+
if (!hasZip && !hasParts) {
|
|
2515
|
+
logger.warn(`Bundle not found for '${host}${routePath}'. Skipping.`, { zipPath, deployId });
|
|
2516
|
+
continue;
|
|
2517
|
+
}
|
|
2518
|
+
|
|
2519
|
+
if (hasParts) shellExec(`${baseCommand} client --merge-zip ${zipPath}`);
|
|
2520
|
+
shellExec(`${baseCommand} client --unzip ${zipPath}`);
|
|
2521
|
+
shellExec(`sudo rm -rf ${zipPath}`);
|
|
2522
|
+
|
|
2523
|
+
// Clean up downloaded part wrapper zips left by --omit-unzip pull
|
|
2524
|
+
if (fs.existsSync(buildDir)) {
|
|
2525
|
+
fs.readdirSync(buildDir)
|
|
2526
|
+
.filter((name) => name.startsWith(`${buildId}.zip.part`) || name.startsWith(`${buildId}.zip-part`))
|
|
2527
|
+
.forEach((partFile) => shellExec(`sudo rm -rf ${buildDir}/${partFile}`));
|
|
2528
|
+
}
|
|
2529
|
+
|
|
2530
|
+
// unzipClientBuild extracts to buildId with trailing '-' stripped
|
|
2531
|
+
// e.g. "build/underpost.net-" → "build/underpost.net"
|
|
2532
|
+
// e.g. "build/app.net-admin" → "build/app.net-admin" (no trailing dash, no change)
|
|
2533
|
+
const extractedDir = `build/${buildId.replace(/-$/, '')}`;
|
|
2534
|
+
if (!fs.existsSync(extractedDir)) {
|
|
2535
|
+
logger.warn(`Extracted build dir not found: ${extractedDir}. Skipping move for '${host}${routePath}'.`);
|
|
2536
|
+
continue;
|
|
2537
|
+
}
|
|
2538
|
+
|
|
2539
|
+
// Destination mirrors the public directory layout used by the server
|
|
2540
|
+
const publicDestPath = routePath === '/' ? `public/${host}` : `public/${host}${routePath}`;
|
|
2541
|
+
if (fs.existsSync(publicDestPath)) shellExec(`sudo rm -rf ${publicDestPath}`);
|
|
2542
|
+
// Ensure parent directory exists for sub-paths
|
|
2543
|
+
if (routePath !== '/') shellExec(`sudo mkdir -p public/${host}`);
|
|
2544
|
+
shellExec(`sudo mv ${extractedDir} ${publicDestPath}`);
|
|
2545
|
+
}
|
|
2546
|
+
}
|
|
2547
|
+
},
|
|
2169
2548
|
};
|
|
2170
2549
|
|
|
2171
2550
|
static API = {
|
package/src/cli/ssh.js
CHANGED
|
@@ -315,7 +315,7 @@ EOF`);
|
|
|
315
315
|
console.log(`group_name : password_x : GID(Internal Group ID) : user_list`.blue);
|
|
316
316
|
console.log(filter ? groupsOut.replaceAll(filter, filter.red) : groupsOut);
|
|
317
317
|
console.log('Users'.bold.blue);
|
|
318
|
-
console.log(`
|
|
318
|
+
console.log(`user : x : UID : GID : GECOS : home_dir : shell`.blue);
|
|
319
319
|
console.log(filter ? usersOut.replaceAll(filter, filter.red) : usersOut);
|
|
320
320
|
}
|
|
321
321
|
|