underpost 3.2.90 → 3.3.0
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/ghpkg.ci.yml +7 -1
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -16
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.github/workflows/release.cd.yml +1 -9
- package/CHANGELOG.md +110 -1
- package/CLI-HELP.md +139 -9
- package/README.md +5 -2
- package/bin/build.js +7 -5
- package/bin/deploy.js +1 -1
- package/deploy/lib/logging.sh +96 -0
- package/deploy/pwa-microservices-template/deploy.sh +72 -0
- package/deploy/release/deploy.sh +62 -0
- package/docker-compose.yml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +5 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-vultr.yaml +52 -0
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/package.json +5 -5
- package/scripts/audit-selinux.sh +64 -0
- package/scripts/coverall-test.sh +24 -0
- package/scripts/gpu-diag.sh +0 -0
- package/scripts/ip-info.sh +0 -0
- package/scripts/k3s-node-setup.sh +18 -15
- package/scripts/kubeadm-node-setup.sh +12 -23
- package/scripts/link-local-underpost-cli.sh +0 -0
- package/scripts/lxd-vm-setup.sh +0 -0
- package/scripts/maas-nat-firewalld.sh +0 -0
- package/scripts/nat-iptables.sh +2 -0
- package/scripts/rhel-grpc-setup.sh +0 -0
- package/scripts/rocky-kickstart.sh +25 -9
- package/scripts/test-monitor.sh +1 -1
- package/src/cli/baremetal.js +1 -2
- package/src/cli/cloud-init.js +1 -1
- package/src/cli/cluster.js +73 -68
- package/src/cli/db.js +9 -2
- package/src/cli/deploy.js +21 -5
- package/src/cli/docker-compose.js +1 -1
- package/src/cli/env.js +1 -1
- package/src/cli/image.js +0 -1
- package/src/cli/index.js +121 -9
- package/src/cli/lxd.js +1 -1
- package/src/cli/monitor.js +1 -1
- package/src/cli/release.js +57 -22
- package/src/cli/repository.js +11 -9
- package/src/cli/run.js +36 -9
- package/src/cli/ssh.js +198 -77
- package/src/cli/system.js +26 -13
- package/src/cli/test.js +1 -1
- package/src/cli/vultr.js +583 -0
- package/src/cli/wireguard.js +2125 -0
- package/src/client-builder/client-build.js +20 -14
- package/src/db/mongo/MongooseDB.js +4 -0
- package/src/index.js +25 -1
- package/src/projects/underpost/catalog-underpost.js +4 -1
- package/src/server/backup.js +1 -1
- package/src/server/conf.js +18 -108
- package/src/server/cron.js +249 -51
- package/src/server/dns.js +100 -6
- package/src/server/environment.js +98 -0
- package/src/server/forward-proxy.js +549 -0
- package/src/server/middlewares.js +56 -1
- package/src/server/process.js +0 -1
- package/src/server/selinux.js +185 -0
- package/src/server/systemd.js +205 -0
- package/src/server/underpost-compression.js +186 -0
- package/src/server/underpost-gateway.js +20 -10
- package/src/server/underpost-ingress.js +18 -2
- package/test/selinux.test.js +71 -0
- package/test/underpost-gateway.test.js +41 -0
- package/test/underpost-ingress.test.js +52 -0
- package/test/wireguard-edge.test.js +1177 -0
package/src/cli/release.js
CHANGED
|
@@ -56,6 +56,10 @@ const buildVersionBumpTargets = () => [
|
|
|
56
56
|
file: 'src/index.js',
|
|
57
57
|
patterns: /(static version = ['"]v)\d+\.\d+\.\d+(?=['"])/g,
|
|
58
58
|
},
|
|
59
|
+
{
|
|
60
|
+
file: 'src/cli/run.js',
|
|
61
|
+
patterns: /(const version = ['"]v)\d+\.\d+\.\d+(?=['"])/g,
|
|
62
|
+
},
|
|
59
63
|
|
|
60
64
|
// ── README + CLI-HELP fallbacks. CLI-HELP is regenerated by `deploy cli-docs`, but bump
|
|
61
65
|
// it too so the file is consistent if the regeneration step is skipped. ──
|
|
@@ -107,6 +111,14 @@ const buildVersionBumpTargets = () => [
|
|
|
107
111
|
recursive: true,
|
|
108
112
|
},
|
|
109
113
|
|
|
114
|
+
// ── Deployment runner scripts carry image defaults passed to deploy/sync commands. ──
|
|
115
|
+
{
|
|
116
|
+
dir: 'deploy',
|
|
117
|
+
match: /\.sh$/,
|
|
118
|
+
patterns: /(underpost\/[a-z0-9-]+:v)\d+\.\d+\.\d+/g,
|
|
119
|
+
recursive: true,
|
|
120
|
+
},
|
|
121
|
+
|
|
110
122
|
// ── GitHub Actions workflows. Single sweep across docker-image.*.ci.yml, engine-*.cd.yml,
|
|
111
123
|
// and the root docker-image.ci.yml — replaces previous three separate loops. ──
|
|
112
124
|
{
|
|
@@ -261,6 +273,19 @@ const bumpAuxiliaryFiles = (oldVersion, newVersion, { dryRun = false } = {}) =>
|
|
|
261
273
|
return report;
|
|
262
274
|
};
|
|
263
275
|
|
|
276
|
+
/**
|
|
277
|
+
* Returns whether a manifest's canonical top-level version would change. Used by dry-runs
|
|
278
|
+
* because bumpp has no non-mutating API option.
|
|
279
|
+
*/
|
|
280
|
+
const wouldBumpManifestVersion = (filePath, newVersion) => {
|
|
281
|
+
try {
|
|
282
|
+
const manifest = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
283
|
+
return typeof manifest.version === 'string' && manifest.version !== newVersion;
|
|
284
|
+
} catch {
|
|
285
|
+
return false;
|
|
286
|
+
}
|
|
287
|
+
};
|
|
288
|
+
|
|
264
289
|
/**
|
|
265
290
|
* Prints a per-file change summary table.
|
|
266
291
|
*/
|
|
@@ -379,8 +404,8 @@ class UnderpostRelease {
|
|
|
379
404
|
* 1. Loads production env and pulls latest code; kills dev servers on ports 4001-4003.
|
|
380
405
|
* 2. Builds and smoke-tests the pwa-microservices-template (aborts on error).
|
|
381
406
|
* 3. Canonical version files: delegates to `bumpp` (driven by `bump.config.js`). Bumps
|
|
382
|
-
* package.json, package-lock.json (root + packages['']),
|
|
383
|
-
* engine-private/conf/**\/package.json.
|
|
407
|
+
* package.json, package-lock.json (root + packages['']), Hardhat's package metadata,
|
|
408
|
+
* and every engine-private/conf/**\/package.json.
|
|
384
409
|
* 4. Auxiliary files: anchored-regex walker over VERSION_BUMP_TARGETS — covers
|
|
385
410
|
* src/index.js, README, cyberia-docs, nexodev docs, manifests (deployment + cronjobs),
|
|
386
411
|
* .github/workflows (image tags, type=raw, UNDERPOST_VERSION build-args), and
|
|
@@ -420,26 +445,36 @@ class UnderpostRelease {
|
|
|
420
445
|
if (!templateOk) return;
|
|
421
446
|
}
|
|
422
447
|
|
|
423
|
-
// ── Canonical version files:
|
|
424
|
-
//
|
|
425
|
-
//
|
|
426
|
-
const
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
448
|
+
// ── Canonical version files: bumpp owns lockfile semantics (root version +
|
|
449
|
+
// packages[''].version). Its API has no non-mutating dry-run, so previews inspect
|
|
450
|
+
// manifest versions directly rather than invoking it. ──
|
|
451
|
+
const canonicalVersionFiles = [
|
|
452
|
+
'package.json',
|
|
453
|
+
'package-lock.json',
|
|
454
|
+
'hardhat/package.json',
|
|
455
|
+
'hardhat/package-lock.json',
|
|
456
|
+
...walkDir('engine-private/conf', /^package\.json$/, true),
|
|
457
|
+
];
|
|
458
|
+
if (dryRun) {
|
|
459
|
+
const pendingCanonicalFiles = canonicalVersionFiles.filter((file) =>
|
|
460
|
+
wouldBumpManifestVersion(file, newVersion),
|
|
461
|
+
);
|
|
462
|
+
logger.info(`bumpp would update ${pendingCanonicalFiles.length} canonical file(s).`);
|
|
463
|
+
for (const file of pendingCanonicalFiles) console.log(` ${file}`);
|
|
464
|
+
} else {
|
|
465
|
+
const { versionBump } = await import('bumpp');
|
|
466
|
+
const bumppResult = await versionBump({
|
|
467
|
+
release: newVersion,
|
|
468
|
+
files: canonicalVersionFiles,
|
|
469
|
+
commit: false,
|
|
470
|
+
tag: false,
|
|
471
|
+
push: false,
|
|
472
|
+
confirm: false,
|
|
473
|
+
recursive: false,
|
|
474
|
+
ignoreScripts: true,
|
|
475
|
+
});
|
|
476
|
+
logger.info(`bumpp updated ${bumppResult?.updatedFiles?.length ?? 0} canonical file(s).`);
|
|
477
|
+
}
|
|
443
478
|
|
|
444
479
|
// ── Auxiliary files: anchored-regex walker over VERSION_BUMP_TARGETS. ──
|
|
445
480
|
const report = bumpAuxiliaryFiles(version, newVersion, { dryRun });
|
package/src/cli/repository.js
CHANGED
|
@@ -11,7 +11,6 @@ import { actionInitLog, loggerFactory } from '../server/logger.js';
|
|
|
11
11
|
import path from 'path';
|
|
12
12
|
import fs from 'fs-extra';
|
|
13
13
|
import {
|
|
14
|
-
getNpmRootPath,
|
|
15
14
|
Config,
|
|
16
15
|
loadConf,
|
|
17
16
|
readConfJson,
|
|
@@ -20,9 +19,9 @@ import {
|
|
|
20
19
|
loadConfServerJson,
|
|
21
20
|
getDataDeploy,
|
|
22
21
|
buildReplicaId,
|
|
23
|
-
writeEnv,
|
|
24
22
|
readConfInstances,
|
|
25
23
|
} from '../server/conf.js';
|
|
24
|
+
import { getNpmRootPath, writeEnv } from '../server/environment.js';
|
|
26
25
|
import { buildClient, unzipClientBuild, mergeClientBuildZip } from '../client-builder/client-build.js';
|
|
27
26
|
import { DefaultConf } from '../../conf.js';
|
|
28
27
|
import Underpost from '../index.js';
|
|
@@ -109,7 +108,7 @@ class UnderpostRepository {
|
|
|
109
108
|
* @param {boolean} [options.changelogNoHash=false] - If true, omits commit hashes from the changelog entries.
|
|
110
109
|
* @param {boolean} [options.remoteUrl=false] - If true, prints the current git remote URL (origin) in plain text and returns.
|
|
111
110
|
* @param {string} [options.switchRepo=''] - If set, switches the remote `origin` to this URL and force-pulls the target branch, overwriting the current working tree.
|
|
112
|
-
* @param {string} [options.targetBranch=''] - Target branch for `switchRepo` (defaults to
|
|
111
|
+
* @param {string} [options.targetBranch=''] - Target branch for `switchRepo` (defaults to the remote's default branch).
|
|
113
112
|
* @memberof UnderpostRepository
|
|
114
113
|
*/
|
|
115
114
|
commit(
|
|
@@ -181,7 +180,7 @@ class UnderpostRepository {
|
|
|
181
180
|
Underpost.repo.switchRemote({
|
|
182
181
|
path: repoPath,
|
|
183
182
|
url: options.switchRepo,
|
|
184
|
-
branch: options.targetBranch
|
|
183
|
+
branch: options.targetBranch,
|
|
185
184
|
});
|
|
186
185
|
return;
|
|
187
186
|
}
|
|
@@ -906,6 +905,7 @@ class UnderpostRepository {
|
|
|
906
905
|
split: options.split || '',
|
|
907
906
|
fullBuild: options.liteBuild ? false : true,
|
|
908
907
|
iconsBuild: options.iconsBuild || false,
|
|
908
|
+
ssrOnly: options.ssr || false,
|
|
909
909
|
});
|
|
910
910
|
for (const replicaDeployId of deployIdSingleReplicas) {
|
|
911
911
|
if (!fs.existsSync(`./engine-private/replica/${replicaDeployId}`)) {
|
|
@@ -917,6 +917,7 @@ class UnderpostRepository {
|
|
|
917
917
|
split: options.split || '',
|
|
918
918
|
liteBuild: options.liteBuild || false,
|
|
919
919
|
iconsBuild: options.iconsBuild || false,
|
|
920
|
+
ssr: options.ssr || false,
|
|
920
921
|
});
|
|
921
922
|
}
|
|
922
923
|
|
|
@@ -1428,14 +1429,15 @@ Prevent build private config repo.`,
|
|
|
1428
1429
|
* @param {object} opts
|
|
1429
1430
|
* @param {string} opts.url - New remote URL (full URL or "owner/repo" short form).
|
|
1430
1431
|
* @param {string} [opts.path='.'] - Path to the git repository.
|
|
1431
|
-
* @param {string} [opts.branch
|
|
1432
|
+
* @param {string} [opts.branch] - Target branch to overwrite the current tree with. Defaults to the remote's default branch.
|
|
1432
1433
|
* @param {string} [opts.remote='origin'] - Remote name to set and fetch from.
|
|
1433
1434
|
* @returns {void}
|
|
1434
1435
|
* @memberof UnderpostRepository
|
|
1435
1436
|
*/
|
|
1436
|
-
switchRemote({ url, path: repoPath = '.', branch = '
|
|
1437
|
+
switchRemote({ url, path: repoPath = '.', branch = '', remote = 'origin' }) {
|
|
1437
1438
|
if (!url) throw new Error('switchRemote requires a target remote url');
|
|
1438
1439
|
if (!fs.existsSync(`${repoPath}/.git`)) throw new Error(`switchRemote: not a git repository: ${repoPath}`);
|
|
1440
|
+
const targetBranch = branch || Underpost.repo.getDefaultBranch(url);
|
|
1439
1441
|
// Token-free URL for the stored remote; auth-injected URL only for the fetch.
|
|
1440
1442
|
let normalized = url;
|
|
1441
1443
|
if (!url.startsWith('http://') && !url.startsWith('https://') && !url.startsWith('git@')) {
|
|
@@ -1445,12 +1447,12 @@ Prevent build private config repo.`,
|
|
|
1445
1447
|
const current = Underpost.repo.getRemoteUrl({ path: repoPath, remote });
|
|
1446
1448
|
if (!current) shellExec(`cd "${repoPath}" && git remote add ${remote} "${normalized}"`);
|
|
1447
1449
|
else shellExec(`cd "${repoPath}" && git remote set-url ${remote} "${normalized}"`);
|
|
1448
|
-
logger.info('switchRemote', { path: repoPath, remote, branch, url: normalized });
|
|
1449
|
-
shellExec(`cd "${repoPath}" && GIT_TERMINAL_PROMPT=0 git fetch --force "${authUrl}" ${
|
|
1450
|
+
logger.info('switchRemote', { path: repoPath, remote, branch: targetBranch, url: normalized });
|
|
1451
|
+
shellExec(`cd "${repoPath}" && GIT_TERMINAL_PROMPT=0 git fetch --force "${authUrl}" ${targetBranch}`);
|
|
1450
1452
|
// reset --hard first clears the worktree so the checkout cannot be blocked
|
|
1451
1453
|
// by conflicting local changes; -B points the target branch at the fetched tip.
|
|
1452
1454
|
shellExec(`cd "${repoPath}" && git reset --hard FETCH_HEAD`);
|
|
1453
|
-
shellExec(`cd "${repoPath}" && git checkout -B ${
|
|
1455
|
+
shellExec(`cd "${repoPath}" && git checkout -B ${targetBranch} FETCH_HEAD`);
|
|
1454
1456
|
},
|
|
1455
1457
|
|
|
1456
1458
|
/**
|
package/src/cli/run.js
CHANGED
|
@@ -4,13 +4,12 @@
|
|
|
4
4
|
* @namespace UnderpostRun
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import { daemonProcess,
|
|
7
|
+
import { daemonProcess, pbcopy, shellCd, shellExec } from '../server/process.js';
|
|
8
8
|
import {
|
|
9
9
|
awaitDeployMonitor,
|
|
10
10
|
buildKindPorts,
|
|
11
11
|
clusterContextFactory,
|
|
12
12
|
clusterTypeFactory,
|
|
13
|
-
cronDeployIdResolve,
|
|
14
13
|
dispatchBuildInstanceEnv,
|
|
15
14
|
deployHostsFactory,
|
|
16
15
|
etcHostFactory,
|
|
@@ -21,7 +20,6 @@ import {
|
|
|
21
20
|
exposeTcpPortsFactory,
|
|
22
21
|
gatewayApiEnabledFactory,
|
|
23
22
|
generateSecurePassword,
|
|
24
|
-
getNpmRootPath,
|
|
25
23
|
instanceHttpRouteRulesFactory,
|
|
26
24
|
instanceInterceptStatusesFactory,
|
|
27
25
|
instancePortFactory,
|
|
@@ -36,7 +34,6 @@ import {
|
|
|
36
34
|
resolveEnvScoped,
|
|
37
35
|
selectConfInstances,
|
|
38
36
|
waitForPort,
|
|
39
|
-
writeEnv,
|
|
40
37
|
clusterInstancesFactory,
|
|
41
38
|
deployTrafficEntriesFactory,
|
|
42
39
|
curlStatusChainFactory,
|
|
@@ -49,6 +46,8 @@ import {
|
|
|
49
46
|
stopPlanFactory,
|
|
50
47
|
trafficFromRoutingInfoFactory,
|
|
51
48
|
} from '../server/conf.js';
|
|
49
|
+
import { cronDeployIdResolve } from '../server/cron.js';
|
|
50
|
+
import { getNpmRootPath, writeEnv } from '../server/environment.js';
|
|
52
51
|
import { actionInitLog, loggerFactory } from '../server/logger.js';
|
|
53
52
|
|
|
54
53
|
import fs from 'fs-extra';
|
|
@@ -127,7 +126,7 @@ const logger = loggerFactory(import.meta);
|
|
|
127
126
|
* @property {string} confServerPath - The configuration server path.
|
|
128
127
|
* @property {string} underpostRoot - The root path of the Underpost installation.
|
|
129
128
|
* @property {string} cmdCronJobs - Pre-script commands to run before cron job execution.
|
|
130
|
-
* @property {string} deployIdCronJobs -
|
|
129
|
+
* @property {string} deployIdCronJobs - Cron deploy-id passed to `cron --setup-start`; unset resolves dd.cron, `none` skips cron setup.
|
|
131
130
|
* @property {string} timezone - The timezone to set.
|
|
132
131
|
* @property {boolean} kubeadm - Whether to run in kubeadm mode.
|
|
133
132
|
* @property {boolean} kind - Whether to run in kind mode.
|
|
@@ -1172,8 +1171,16 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1172
1171
|
if (!validVersion) throw new Error('Version mismatch');
|
|
1173
1172
|
}
|
|
1174
1173
|
if (options.timezone !== 'none') shellExec(`${baseCommand} run${baseClusterCommand} tz`);
|
|
1175
|
-
if (options.deployIdCronJobs !== 'none')
|
|
1176
|
-
|
|
1174
|
+
if (options.deployIdCronJobs !== 'none') {
|
|
1175
|
+
// --deploy-id-cron-jobs overrides the cron deploy-id; unset falls back to
|
|
1176
|
+
// dd.cron, the same source `cron --setup-start` resolves against on its own.
|
|
1177
|
+
const cronDeployId = options.deployIdCronJobs || cronDeployIdResolve() || '';
|
|
1178
|
+
if (cronDeployId && !/^[a-zA-Z0-9._,-]+$/.test(cronDeployId))
|
|
1179
|
+
throw new Error(`Invalid cron deploy-id: ${cronDeployId}`);
|
|
1180
|
+
shellExec(
|
|
1181
|
+
`node bin cron${cronDeployId ? ` ${cronDeployId}` : ''}${baseClusterCommand}${clusterFlag} --setup-start --git --apply`,
|
|
1182
|
+
);
|
|
1183
|
+
}
|
|
1177
1184
|
}
|
|
1178
1185
|
|
|
1179
1186
|
const currentTraffic = isDeployRunnerContext(path, options)
|
|
@@ -1220,7 +1227,7 @@ echo -e "[code]\nname=Visual Studio Code\nbaseurl=https://packages.microsoft.com
|
|
|
1220
1227
|
// runner. Build the host-side SSR documents before generating routes unless
|
|
1221
1228
|
// the caller explicitly selected a pre-built bundle workflow.
|
|
1222
1229
|
if (isDeployRunnerContext(path, options) && !options.skipFullBuild)
|
|
1223
|
-
shellExec(`${baseCommand} client ${deployId} ${env}`);
|
|
1230
|
+
shellExec(`${baseCommand} client ${deployId} ${env} --ssr`);
|
|
1224
1231
|
|
|
1225
1232
|
shellExec(
|
|
1226
1233
|
`${baseCommand} deploy${clusterFlag} --build-manifest --sync --info-router --replicas ${replicas} --node ${node}${
|
|
@@ -3090,7 +3097,7 @@ EOF`);
|
|
|
3090
3097
|
const hostListenResult = etcHostFactory(hosts);
|
|
3091
3098
|
logger.info(hostListenResult.renderHosts);
|
|
3092
3099
|
}
|
|
3093
|
-
const version = 'v3.
|
|
3100
|
+
const version = 'v3.3.0';
|
|
3094
3101
|
const instanceOptionsFactory = (deployId, instanceId) => ({
|
|
3095
3102
|
...options,
|
|
3096
3103
|
...clusterContextFactory(clusterType),
|
|
@@ -4906,6 +4913,26 @@ EOF`;
|
|
|
4906
4913
|
}
|
|
4907
4914
|
},
|
|
4908
4915
|
|
|
4916
|
+
/**
|
|
4917
|
+
* @method kubeadm-wireguard
|
|
4918
|
+
* @description
|
|
4919
|
+
* Configures Calico to keep using the Kubernetes NodeInternalIP after WireGuard
|
|
4920
|
+
* is added to the node. This prevents the WireGuard interface (for example
|
|
4921
|
+
* 10.0.0.2) from being selected as the Calico/BGP node address.
|
|
4922
|
+
*
|
|
4923
|
+
* @param {string} path - Unused.
|
|
4924
|
+
* @param {UnderpostRunDefaultOptions} options - The default underpost runner options.
|
|
4925
|
+
* @memberof UnderpostRun
|
|
4926
|
+
*/
|
|
4927
|
+
'kubeadm-wireguard': (path, options = DEFAULT_OPTION) => {
|
|
4928
|
+
shellExec(`kubectl patch installation.operator.tigera.io default --type='json' \
|
|
4929
|
+
-p='[
|
|
4930
|
+
{"op":"replace","path":"/spec/calicoNetwork/nodeAddressAutodetectionV4","value":{"kubernetes":"NodeInternalIP"}}
|
|
4931
|
+
]'`);
|
|
4932
|
+
|
|
4933
|
+
shellExec(`kubectl rollout restart daemonset/calico-node -n calico-system`);
|
|
4934
|
+
},
|
|
4935
|
+
|
|
4909
4936
|
/**
|
|
4910
4937
|
* @method build-cluster-deployment-manifests
|
|
4911
4938
|
* @description Builds deployment manifests for both production and development environments using `node bin deploy --build-manifest`, syncing them, and setting replicas to 1 for the `dd` deployment.
|