underpost 3.0.1 → 3.0.2
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/CHANGELOG.md +56 -1
- package/CLI-HELP.md +2 -4
- package/README.md +2 -2
- package/bin/build.js +6 -0
- package/bin/deploy.js +18 -26
- package/bin/file.js +3 -0
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/manifests/ipfs/configmap.yaml +7 -0
- package/package.json +1 -1
- package/src/api/file/file.controller.js +3 -13
- package/src/api/file/file.ref.json +0 -21
- package/src/cli/cluster.js +30 -38
- package/src/cli/index.js +0 -1
- package/src/cli/run.js +14 -0
- package/src/client/components/core/LoadingAnimation.js +2 -3
- package/src/client/components/core/Modal.js +1 -1
- package/src/index.js +1 -1
- package/src/runtime/express/Express.js +1 -1
- package/src/server/auth.js +18 -18
- package/src/server/ipfs-client.js +433 -0
- package/bin/ssl.js +0 -63
package/CHANGELOG.md
CHANGED
|
@@ -1,6 +1,61 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 2026-02
|
|
3
|
+
## 2026-03-02
|
|
4
|
+
|
|
5
|
+
### engine-cyberia
|
|
6
|
+
|
|
7
|
+
- Add test in engine-cyberia cd workflow ([642d7e36](https://github.com/underpostnet/engine/commit/642d7e36155b6db5dbd36e19767dd95f146ceaf5))
|
|
8
|
+
- Add build dd-cyberia shape generator exclusive module files ([188f563a](https://github.com/underpostnet/engine/commit/188f563a61d7f6bf36bef93cdd18d4e1304f9747))
|
|
9
|
+
- Fix ObjectLayerEngineViewer return to list button click ([aeaead6f](https://github.com/underpostnet/engine/commit/aeaead6f5c67a03449c63c584976e9a73ccc953d))
|
|
10
|
+
- Improve static generations assets in object layer generation CLI ([06694d92](https://github.com/underpostnet/engine/commit/06694d92ea1ad849e745f561b8ec9a48bfa66056))
|
|
11
|
+
- Implements deterministic object layer generation CLI ([f70c9841](https://github.com/underpostnet/engine/commit/f70c9841ef2efc9187c87427cc465505487766db))
|
|
12
|
+
- Implement shape generator module ([5741a38b](https://github.com/underpostnet/engine/commit/5741a38bcfb8c1c4e0ef5053a2a6a73ff50a3879))
|
|
13
|
+
- Fix remove of ag grid table delete object layer delete logic ([e98953cd](https://github.com/underpostnet/engine/commit/e98953cd29767ca44c2362997f0af40cd538371b))
|
|
14
|
+
- Centralize Object Layer Logic and add js docs ([ff8eefed](https://github.com/underpostnet/engine/commit/ff8eefed08349a1e3390379f760c0d9eb20aecca))
|
|
15
|
+
- ObjectLayer Engine Viewer Enhancements ([0ee052e5](https://github.com/underpostnet/engine/commit/0ee052e5231f7b55576595a817742970c90cd056))
|
|
16
|
+
- Add metada json editor of object layers ([abe7448f](https://github.com/underpostnet/engine/commit/abe7448f5ed7429ba1f5c5d01ed94c5c70323638))
|
|
17
|
+
- Remove helia pyntype logic ([2b443d1c](https://github.com/underpostnet/engine/commit/2b443d1c0ed2261e27d5be54903c9a37cff29dd5))
|
|
18
|
+
- Object Layer Deletion Cleanup IPFS ([a2dcdf23](https://github.com/underpostnet/engine/commit/a2dcdf238c32d5b5237f0650232aca0c0823f044))
|
|
19
|
+
- Add Public GET Access for File Object Atlas ([826317fe](https://github.com/underpostnet/engine/commit/826317fe21dfd0b77196ef343b31461c45b5eb72))
|
|
20
|
+
- Allow Cross-Origin on GET methods file, object-layer, and atlas-sprite-sheet api. ([6801839c](https://github.com/underpostnet/engine/commit/6801839cc461dbec6ca205b035ea844415779e85))
|
|
21
|
+
- Add DISABLE_API_RATE_LIMIT env option ([ae72885c](https://github.com/underpostnet/engine/commit/ae72885c1178846067db52b62455d804dbe4eeba))
|
|
22
|
+
|
|
23
|
+
### client-core
|
|
24
|
+
|
|
25
|
+
- Fix main-body-btn-container hide logic ([221f8bfc](https://github.com/underpostnet/engine/commit/221f8bfc262048e1ca226f66f0dfab9891db3fd5))
|
|
26
|
+
|
|
27
|
+
### runtime-express
|
|
28
|
+
|
|
29
|
+
- Fix express rate limit trust proxy ([ed19e729](https://github.com/underpostnet/engine/commit/ed19e729eafb59d46504fb1ebe89e4bd91c05d7e))
|
|
30
|
+
|
|
31
|
+
### cli-cluster
|
|
32
|
+
|
|
33
|
+
- Remove unused full flag ([13df39f5](https://github.com/underpostnet/engine/commit/13df39f508d65b61378ccfca4f7bfc427dcf5fa5))
|
|
34
|
+
|
|
35
|
+
### ipfs
|
|
36
|
+
|
|
37
|
+
- Add ipfs client stable json stringify in addJsonToIpfs method ([c2aaf56a](https://github.com/underpostnet/engine/commit/c2aaf56a4bfc4f06147818ec5681567e27967f41))
|
|
38
|
+
- Fix config map IPFS Cluster daemon bind ([7e6df963](https://github.com/underpostnet/engine/commit/7e6df963ba6da1fdc96ac5b6ab844a789901f61b))
|
|
39
|
+
- server ipfs client and object layer atlas sprite sheet ipfs integration ([781e35c4](https://github.com/underpostnet/engine/commit/781e35c4903380df9e7dce7cf5d9275387a46029))
|
|
40
|
+
- Implement ipfs api user-pin and client component service ([1b12e8df](https://github.com/underpostnet/engine/commit/1b12e8df6af21e1dd2edc156e176072f25c9a433))
|
|
41
|
+
|
|
42
|
+
### cli-run
|
|
43
|
+
|
|
44
|
+
- Implements expose-ipfs runner ([765772b8](https://github.com/underpostnet/engine/commit/765772b8fb1e7b397560464d1dc6dea0b70a9b7f))
|
|
45
|
+
|
|
46
|
+
### engine-core
|
|
47
|
+
|
|
48
|
+
- Clean up legacy logic and json file model ref ([b4c62a2c](https://github.com/underpostnet/engine/commit/b4c62a2cfe4fea0212be644ce333464a81056f6f))
|
|
49
|
+
|
|
50
|
+
### bin-build
|
|
51
|
+
|
|
52
|
+
- Add missing packagejson overrides on dd-cyberia build repository workflow ([7ece9ed5](https://github.com/underpostnet/engine/commit/7ece9ed5500e83a1baedc4d78fd889bca6ecac3c))
|
|
53
|
+
|
|
54
|
+
## New release v:3.0.1 (2026-02-22)
|
|
55
|
+
|
|
56
|
+
### engine-core
|
|
57
|
+
|
|
58
|
+
- Remove ENABLE_FILE_LOGS to default dev adn test env ([727486dc](https://github.com/underpostnet/engine/commit/727486dc4030921c9d1f6a7035eb1a240569fa74))
|
|
4
59
|
|
|
5
60
|
### gitlab
|
|
6
61
|
|
package/CLI-HELP.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
## underpost ci/cd cli v3.0.
|
|
1
|
+
## underpost ci/cd cli v3.0.2
|
|
2
2
|
|
|
3
3
|
### Usage: `underpost [options] [command]`
|
|
4
4
|
```
|
|
@@ -379,8 +379,6 @@ Options:
|
|
|
379
379
|
--dedicated-gpu Initializes the cluster with dedicated
|
|
380
380
|
GPU base resources and environment
|
|
381
381
|
settings.
|
|
382
|
-
--full Initializes the cluster with all
|
|
383
|
-
available statefulsets and services.
|
|
384
382
|
--ns-use <ns-name> Switches the current Kubernetes context
|
|
385
383
|
to the specified namespace (creates if
|
|
386
384
|
it doesn't exist).
|
|
@@ -835,7 +833,7 @@ Options:
|
|
|
835
833
|
Runs specified scripts using various runners.
|
|
836
834
|
|
|
837
835
|
Arguments:
|
|
838
|
-
runner-id The runner ID to run. Options: dev-cluster,metadata,svc-ls,svc-rm,ssh-deploy-info,dev-hosts-expose,dev-hosts-restore,cluster-build,template-deploy,template-deploy-image,clean,pull,release-deploy,ssh-deploy,ide,crypto-policy,sync,stop,ssh-deploy-stop,ssh-deploy-db-rollback,ssh-deploy-db,ssh-deploy-db-status,tz,get-proxy,instance-promote,instance,ls-deployments,host-update,dd-container,ip-info,db-client,git-conf,promote,metrics,cluster,deploy,disk-clean,disk-devices,disk-usage,dev,service,etc-hosts,sh,log,ps,ptls,release-cmt,deploy-test,sync-replica,tf-vae-test,spark-template,rmi,kill,secret,underpost-config,gpu-env,tf-gpu-test,deploy-job.
|
|
836
|
+
runner-id The runner ID to run. Options: dev-cluster,ipfs-expose,metadata,svc-ls,svc-rm,ssh-deploy-info,dev-hosts-expose,dev-hosts-restore,cluster-build,template-deploy,template-deploy-image,clean,pull,release-deploy,ssh-deploy,ide,crypto-policy,sync,stop,ssh-deploy-stop,ssh-deploy-db-rollback,ssh-deploy-db,ssh-deploy-db-status,tz,get-proxy,instance-promote,instance,ls-deployments,host-update,dd-container,ip-info,db-client,git-conf,promote,metrics,cluster,deploy,disk-clean,disk-devices,disk-usage,dev,service,etc-hosts,sh,log,ps,ptls,release-cmt,deploy-test,sync-replica,tf-vae-test,spark-template,rmi,kill,secret,underpost-config,gpu-env,tf-gpu-test,deploy-job.
|
|
839
837
|
path The input value, identifier, or path for the operation.
|
|
840
838
|
|
|
841
839
|
Options:
|
package/README.md
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
|
|
17
17
|
<div align="center">
|
|
18
18
|
|
|
19
|
-
[](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml) [](https://www.npmjs.com/package/underpost) [](https://github.com/underpostnet/engine/actions/workflows/docker-image.yml) [](https://github.com/underpostnet/engine/actions/workflows/coverall.ci.yml) [](https://www.npmjs.com/package/underpost) [](https://socket.dev/npm/package/underpost/overview/3.0.2) [](https://coveralls.io/github/underpostnet/engine?branch=master) [](https://www.npmjs.org/package/underpost) [](https://www.npmjs.com/package/underpost)
|
|
20
20
|
|
|
21
21
|
</div>
|
|
22
22
|
|
|
@@ -60,7 +60,7 @@ npm run dev
|
|
|
60
60
|
|
|
61
61
|
<a target="_top" href="https://www.nexodev.org/docs?cid=src">See Docs here.</a>
|
|
62
62
|
|
|
63
|
-
## underpost ci/cd cli v3.0.
|
|
63
|
+
## underpost ci/cd cli v3.0.2
|
|
64
64
|
|
|
65
65
|
### Usage: `underpost [options] [command]`
|
|
66
66
|
```
|
package/bin/build.js
CHANGED
|
@@ -172,6 +172,7 @@ const { DefaultConf } = await import(`../conf.${confName}.js`);
|
|
|
172
172
|
...packageJson.dependencies,
|
|
173
173
|
...CyberiaDependencies,
|
|
174
174
|
};
|
|
175
|
+
packageJson.overrides = originPackageJson.overrides;
|
|
175
176
|
fs.writeFileSync(`${basePath}/bin/index.js`, fs.readFileSync(`./bin/cyberia.js`, 'utf8'), 'utf8');
|
|
176
177
|
fs.copyFileSync(`./src/api/object-layer/README.md`, `${basePath}/README.md`);
|
|
177
178
|
fs.copySync(`./hardhat`, `${basePath}/hardhat`);
|
|
@@ -180,6 +181,9 @@ const { DefaultConf } = await import(`../conf.${confName}.js`);
|
|
|
180
181
|
'/src/client/ssr/pages/CyberiaServerMetrics.js',
|
|
181
182
|
'/src/server/object-layer.js',
|
|
182
183
|
'/src/server/atlas-sprite-sheet-generator.js',
|
|
184
|
+
'/src/server/shape-generator.js',
|
|
185
|
+
'/src/server/semantic-layer-generator.js',
|
|
186
|
+
'/test/shape-generator.test.js',
|
|
183
187
|
])
|
|
184
188
|
fs.copySync(`.${path}`, `${basePath}${path}`);
|
|
185
189
|
|
|
@@ -216,4 +220,6 @@ const { DefaultConf } = await import(`../conf.${confName}.js`);
|
|
|
216
220
|
if (fs.existsSync(`./src/ws/${confName.split('-')[1]}`)) {
|
|
217
221
|
fs.copySync(`./src/ws/${confName.split('-')[1]}`, `${basePath}/src/ws/${confName.split('-')[1]}`);
|
|
218
222
|
}
|
|
223
|
+
shellExec(`cd ${basePath} && npm install --ignore-scripts`);
|
|
224
|
+
shellExec(`cd ${basePath} && npm run fix`);
|
|
219
225
|
}
|
package/bin/deploy.js
CHANGED
|
@@ -374,7 +374,9 @@ try {
|
|
|
374
374
|
shellExec(`node bin run kill 4002`);
|
|
375
375
|
shellExec(`node bin run kill 4003`);
|
|
376
376
|
shellExec(`npm run update-template`);
|
|
377
|
-
shellExec(
|
|
377
|
+
shellExec(
|
|
378
|
+
`cd ../pwa-microservices-template && npm install && echo "\nENABLE_FILE_LOGS=true" >> .env.development`,
|
|
379
|
+
);
|
|
378
380
|
shellExec(`cd ../pwa-microservices-template && npm run build && timeout 5s npm run dev`, {
|
|
379
381
|
async: true,
|
|
380
382
|
});
|
|
@@ -509,33 +511,23 @@ ${shellExec(`git log | grep Author: | sort -u`, { stdout: true }).split(`\n`).jo
|
|
|
509
511
|
// https://besu.hyperledger.org/
|
|
510
512
|
// https://github.com/hyperledger/besu/archive/refs/tags/24.9.1.tar.gz
|
|
511
513
|
|
|
512
|
-
|
|
513
|
-
case 'linux':
|
|
514
|
-
{
|
|
515
|
-
shellCd(`..`);
|
|
516
|
-
|
|
517
|
-
// Download the Linux binary
|
|
518
|
-
shellExec(`wget https://github.com/hyperledger/besu/releases/download/24.9.1/besu-24.9.1.tar.gz`);
|
|
514
|
+
shellCd(`..`);
|
|
519
515
|
|
|
520
|
-
|
|
521
|
-
|
|
516
|
+
// Download the Linux binary
|
|
517
|
+
shellExec(`wget https://github.com/hyperledger/besu/releases/download/24.9.1/besu-24.9.1.tar.gz`);
|
|
522
518
|
|
|
523
|
-
|
|
519
|
+
// Unzip the file:
|
|
520
|
+
shellExec(`tar -xvzf besu-24.9.1.tar.gz`);
|
|
524
521
|
|
|
525
|
-
|
|
522
|
+
shellCd(`besu-24.9.1`);
|
|
526
523
|
|
|
527
|
-
|
|
528
|
-
// export PATH=$PATH:/home/dd/besu-24.9.1/bin
|
|
524
|
+
shellExec(`bin/besu --help`);
|
|
529
525
|
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
}
|
|
533
|
-
|
|
534
|
-
break;
|
|
526
|
+
// Set env path
|
|
527
|
+
// export PATH=$PATH:/home/dd/besu-24.9.1/bin
|
|
535
528
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
}
|
|
529
|
+
// Open src
|
|
530
|
+
// shellExec(`sudo code /home/dd/besu-24.9.1 --user-data-dir="/root/.vscode-root" --no-sandbox`);
|
|
539
531
|
|
|
540
532
|
break;
|
|
541
533
|
}
|
|
@@ -977,10 +969,10 @@ nvidia/gpu-operator \
|
|
|
977
969
|
`${key}`.toUpperCase().match('MAC')
|
|
978
970
|
? 'changethis'
|
|
979
971
|
: isNaN(parseFloat(privateEnv[key]))
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
972
|
+
? `${privateEnv[key]}`.match(`@`)
|
|
973
|
+
? 'admin@default.net'
|
|
974
|
+
: 'changethis'
|
|
975
|
+
: privateEnv[key];
|
|
984
976
|
}
|
|
985
977
|
return env;
|
|
986
978
|
};
|
package/bin/file.js
CHANGED
|
@@ -97,6 +97,9 @@ try {
|
|
|
97
97
|
'./manifests/deployment/dd-template-development',
|
|
98
98
|
'./src/server/object-layer.js',
|
|
99
99
|
'./src/server/atlas-sprite-sheet-generator.js',
|
|
100
|
+
'./src/server/shape-generator.js',
|
|
101
|
+
'./src/server/semantic-layer-generator.js',
|
|
102
|
+
'./test/shape-generator.test.js',
|
|
100
103
|
'bin/cyberia.js',
|
|
101
104
|
]) {
|
|
102
105
|
if (fs.existsSync(deletePath)) fs.removeSync('../pwa-microservices-template/' + deletePath);
|
|
@@ -17,7 +17,7 @@ spec:
|
|
|
17
17
|
spec:
|
|
18
18
|
containers:
|
|
19
19
|
- name: dd-default-development-blue
|
|
20
|
-
image: localhost/rockylinux9-underpost:v3.0.
|
|
20
|
+
image: localhost/rockylinux9-underpost:v3.0.2
|
|
21
21
|
# resources:
|
|
22
22
|
# requests:
|
|
23
23
|
# memory: "124Ki"
|
|
@@ -100,7 +100,7 @@ spec:
|
|
|
100
100
|
spec:
|
|
101
101
|
containers:
|
|
102
102
|
- name: dd-default-development-green
|
|
103
|
-
image: localhost/rockylinux9-underpost:v3.0.
|
|
103
|
+
image: localhost/rockylinux9-underpost:v3.0.2
|
|
104
104
|
# resources:
|
|
105
105
|
# requests:
|
|
106
106
|
# memory: "124Ki"
|
|
@@ -18,7 +18,7 @@ spec:
|
|
|
18
18
|
spec:
|
|
19
19
|
containers:
|
|
20
20
|
- name: dd-test-development-blue
|
|
21
|
-
image: localhost/rockylinux9-underpost:v3.0.
|
|
21
|
+
image: localhost/rockylinux9-underpost:v3.0.2
|
|
22
22
|
|
|
23
23
|
command:
|
|
24
24
|
- /bin/sh
|
|
@@ -103,7 +103,7 @@ spec:
|
|
|
103
103
|
spec:
|
|
104
104
|
containers:
|
|
105
105
|
- name: dd-test-development-green
|
|
106
|
-
image: localhost/rockylinux9-underpost:v3.0.
|
|
106
|
+
image: localhost/rockylinux9-underpost:v3.0.2
|
|
107
107
|
|
|
108
108
|
command:
|
|
109
109
|
- /bin/sh
|
|
@@ -16,6 +16,13 @@ data:
|
|
|
16
16
|
ipfs-cluster-service init
|
|
17
17
|
fi
|
|
18
18
|
|
|
19
|
+
# Bind cluster APIs to 0.0.0.0 so they are reachable from other pods.
|
|
20
|
+
# By default ipfs-cluster listens on 127.0.0.1 for REST (9094),
|
|
21
|
+
# Proxy (9095) and Pinning Service (9097).
|
|
22
|
+
sed -i 's|/ip4/127\.0\.0\.1/tcp/9094|/ip4/0.0.0.0/tcp/9094|g' /data/ipfs-cluster/service.json
|
|
23
|
+
sed -i 's|/ip4/127\.0\.0\.1/tcp/9095|/ip4/0.0.0.0/tcp/9095|g' /data/ipfs-cluster/service.json
|
|
24
|
+
sed -i 's|/ip4/127\.0\.0\.1/tcp/9097|/ip4/0.0.0.0/tcp/9097|g' /data/ipfs-cluster/service.json
|
|
25
|
+
|
|
19
26
|
PEER_HOSTNAME=$(cat /proc/sys/kernel/hostname)
|
|
20
27
|
|
|
21
28
|
if echo "${PEER_HOSTNAME}" | grep -q "^ipfs-cluster-0"; then
|
package/package.json
CHANGED
|
@@ -19,22 +19,12 @@ const FileController = {
|
|
|
19
19
|
},
|
|
20
20
|
get: async (req, res, options) => {
|
|
21
21
|
try {
|
|
22
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
23
|
+
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
22
24
|
const result = await FileService.get(req, res, options);
|
|
23
25
|
if (result instanceof Buffer) {
|
|
24
|
-
|
|
25
|
-
process.env.NODE_ENV === 'development' ||
|
|
26
|
-
req.hostname === options.host ||
|
|
27
|
-
(options.origins && options.origins.find((o) => o.match(req.hostname)))
|
|
28
|
-
) {
|
|
29
|
-
res.set('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
30
|
-
return res.status(200).end(result);
|
|
31
|
-
}
|
|
32
|
-
return res.status(403).json({
|
|
33
|
-
status: 'error',
|
|
34
|
-
message: 'Forbidden',
|
|
35
|
-
});
|
|
26
|
+
return res.status(200).end(result);
|
|
36
27
|
}
|
|
37
|
-
|
|
38
28
|
return res.status(200).json({
|
|
39
29
|
status: 'success',
|
|
40
30
|
data: result,
|
|
@@ -5,27 +5,6 @@
|
|
|
5
5
|
"logo": true
|
|
6
6
|
}
|
|
7
7
|
},
|
|
8
|
-
{
|
|
9
|
-
"api": "cyberia-biome",
|
|
10
|
-
"model": {
|
|
11
|
-
"fileId": true,
|
|
12
|
-
"topLevelColorFileId": true
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"api": "cyberia-tile",
|
|
17
|
-
"model": {
|
|
18
|
-
"fileId": true
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"api": "cyberia-world",
|
|
23
|
-
"model": {
|
|
24
|
-
"adjacentFace": {
|
|
25
|
-
"fileId": true
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
8
|
{
|
|
30
9
|
"api": "document",
|
|
31
10
|
"model": {
|
package/src/cli/cluster.js
CHANGED
|
@@ -37,7 +37,6 @@ class UnderpostCluster {
|
|
|
37
37
|
* @param {boolean} [options.postgresql=false] - Deploy PostgreSQL.
|
|
38
38
|
* @param {boolean} [options.valkey=false] - Deploy Valkey.
|
|
39
39
|
* @param {boolean} [options.ipfs=false] - Deploy ipfs-cluster statefulset.
|
|
40
|
-
* @param {boolean} [options.full=false] - Deploy a full set of common components.
|
|
41
40
|
* @param {boolean} [options.info=false] - Display extensive Kubernetes cluster information.
|
|
42
41
|
* @param {boolean} [options.certManager=false] - Deploy Cert-Manager for certificate management.
|
|
43
42
|
* @param {boolean} [options.listPods=false] - List Kubernetes pods.
|
|
@@ -75,7 +74,6 @@ class UnderpostCluster {
|
|
|
75
74
|
postgresql: false,
|
|
76
75
|
valkey: false,
|
|
77
76
|
ipfs: false,
|
|
78
|
-
full: false,
|
|
79
77
|
info: false,
|
|
80
78
|
certManager: false,
|
|
81
79
|
listPods: false,
|
|
@@ -102,26 +100,22 @@ class UnderpostCluster {
|
|
|
102
100
|
replicas: '',
|
|
103
101
|
},
|
|
104
102
|
) {
|
|
105
|
-
|
|
106
|
-
if (options.initHost === true) return Underpost.cluster.initHost();
|
|
103
|
+
if (options.initHost) return Underpost.cluster.initHost();
|
|
107
104
|
|
|
108
|
-
|
|
109
|
-
if (options.uninstallHost === true) return Underpost.cluster.uninstallHost();
|
|
105
|
+
if (options.uninstallHost) return Underpost.cluster.uninstallHost();
|
|
110
106
|
|
|
111
|
-
|
|
112
|
-
if (options.config === true) return Underpost.cluster.config();
|
|
107
|
+
if (options.config) return Underpost.cluster.config();
|
|
113
108
|
|
|
114
|
-
|
|
115
|
-
if (options.chown === true) return Underpost.cluster.chown();
|
|
109
|
+
if (options.chown) return Underpost.cluster.chown();
|
|
116
110
|
|
|
117
111
|
const npmRoot = getNpmRootPath();
|
|
118
|
-
const underpostRoot = options
|
|
112
|
+
const underpostRoot = options.dev ? '.' : `${npmRoot}/underpost`;
|
|
119
113
|
|
|
120
|
-
if (options.listPods
|
|
114
|
+
if (options.listPods) return console.table(Underpost.deploy.get(podName ?? undefined));
|
|
121
115
|
// Set default namespace if not specified
|
|
122
116
|
if (!options.namespace) options.namespace = 'default';
|
|
123
117
|
|
|
124
|
-
if (options.nsUse
|
|
118
|
+
if (options.nsUse) {
|
|
125
119
|
// Verify if namespace exists, create if not
|
|
126
120
|
const namespaceExists = shellExec(`kubectl get namespace ${options.nsUse} --ignore-not-found -o name`, {
|
|
127
121
|
stdout: true,
|
|
@@ -142,8 +136,8 @@ class UnderpostCluster {
|
|
|
142
136
|
}
|
|
143
137
|
|
|
144
138
|
// Reset Kubernetes cluster components (Kind/Kubeadm/K3s) and container runtimes
|
|
145
|
-
if (options.reset
|
|
146
|
-
const clusterType = options.k3s
|
|
139
|
+
if (options.reset) {
|
|
140
|
+
const clusterType = options.k3s ? 'k3s' : options.kubeadm ? 'kubeadm' : 'kind';
|
|
147
141
|
return await Underpost.cluster.safeReset({
|
|
148
142
|
underpostRoot,
|
|
149
143
|
removeVolumeHostPaths: options.removeVolumeHostPaths,
|
|
@@ -160,7 +154,7 @@ class UnderpostCluster {
|
|
|
160
154
|
// --- Kubeadm/Kind/K3s Cluster Initialization ---
|
|
161
155
|
if (!alreadyKubeadmCluster && !alreadyKindCluster && !alreadyK3sCluster) {
|
|
162
156
|
Underpost.cluster.config();
|
|
163
|
-
if (options.k3s
|
|
157
|
+
if (options.k3s) {
|
|
164
158
|
logger.info('Initializing K3s control plane...');
|
|
165
159
|
// Install K3s
|
|
166
160
|
logger.info('Installing K3s...');
|
|
@@ -172,7 +166,7 @@ class UnderpostCluster {
|
|
|
172
166
|
logger.info('Waiting for K3s to be ready...');
|
|
173
167
|
shellExec(`sudo systemctl is-active --wait k3s || sudo systemctl wait --for=active k3s.service`);
|
|
174
168
|
logger.info('K3s service is active.');
|
|
175
|
-
} else if (options.kubeadm
|
|
169
|
+
} else if (options.kubeadm) {
|
|
176
170
|
logger.info('Initializing Kubeadm control plane...');
|
|
177
171
|
// Set default values if not provided
|
|
178
172
|
const podNetworkCidr = options.podNetworkCidr || '192.168.0.0/16';
|
|
@@ -208,7 +202,7 @@ class UnderpostCluster {
|
|
|
208
202
|
logger.info('Initializing Kind cluster...');
|
|
209
203
|
shellExec(
|
|
210
204
|
`cd ${underpostRoot}/manifests && kind create cluster --config kind-config${
|
|
211
|
-
options
|
|
205
|
+
options.dev ? '-dev' : ''
|
|
212
206
|
}.yaml`,
|
|
213
207
|
);
|
|
214
208
|
Underpost.cluster.chown('kind'); // Pass 'kind' to chown
|
|
@@ -218,14 +212,12 @@ class UnderpostCluster {
|
|
|
218
212
|
// --- Optional Component Deployments (Databases, Ingress, Cert-Manager) ---
|
|
219
213
|
// These deployments happen after the base cluster is up.
|
|
220
214
|
|
|
221
|
-
if (options.
|
|
215
|
+
if (options.dedicatedGpu) {
|
|
222
216
|
shellExec(`node ${underpostRoot}/bin/deploy nvidia-gpu-operator`);
|
|
223
|
-
shellExec(
|
|
224
|
-
`node ${underpostRoot}/bin/deploy kubeflow-spark-operator${options.kubeadm === true ? ' kubeadm' : ''}`,
|
|
225
|
-
);
|
|
217
|
+
shellExec(`node ${underpostRoot}/bin/deploy kubeflow-spark-operator${options.kubeadm ? ' kubeadm' : ''}`);
|
|
226
218
|
}
|
|
227
219
|
|
|
228
|
-
if (options.grafana
|
|
220
|
+
if (options.grafana) {
|
|
229
221
|
shellExec(`kubectl delete deployment grafana -n ${options.namespace} --ignore-not-found`);
|
|
230
222
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/grafana -n ${options.namespace}`);
|
|
231
223
|
const yaml = `${fs
|
|
@@ -238,7 +230,7 @@ EOF
|
|
|
238
230
|
`);
|
|
239
231
|
}
|
|
240
232
|
|
|
241
|
-
if (options.prom
|
|
233
|
+
if (options.prom) {
|
|
242
234
|
shellExec(`kubectl delete deployment prometheus --ignore-not-found`);
|
|
243
235
|
shellExec(`kubectl delete configmap prometheus-config --ignore-not-found`);
|
|
244
236
|
shellExec(`kubectl delete service prometheus --ignore-not-found`);
|
|
@@ -257,8 +249,8 @@ EOF
|
|
|
257
249
|
`);
|
|
258
250
|
}
|
|
259
251
|
|
|
260
|
-
if (options.
|
|
261
|
-
if (options.pullImage
|
|
252
|
+
if (options.valkey) {
|
|
253
|
+
if (options.pullImage) Underpost.cluster.pullImage('valkey/valkey:latest', options);
|
|
262
254
|
shellExec(`kubectl delete statefulset valkey-service -n ${options.namespace} --ignore-not-found`);
|
|
263
255
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/valkey -n ${options.namespace}`);
|
|
264
256
|
await Underpost.test.statusMonitor('valkey-service', 'Running', 'pods', 1000, 60 * 10);
|
|
@@ -266,17 +258,17 @@ EOF
|
|
|
266
258
|
if (options.ipfs) {
|
|
267
259
|
await Underpost.ipfs.deploy(options, underpostRoot);
|
|
268
260
|
}
|
|
269
|
-
if (options.
|
|
261
|
+
if (options.mariadb) {
|
|
270
262
|
shellExec(
|
|
271
263
|
`sudo kubectl create secret generic mariadb-secret --from-file=username=/home/dd/engine/engine-private/mariadb-username --from-file=password=/home/dd/engine/engine-private/mariadb-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
|
|
272
264
|
);
|
|
273
265
|
shellExec(`kubectl delete statefulset mariadb-statefulset -n ${options.namespace} --ignore-not-found`);
|
|
274
266
|
|
|
275
|
-
if (options.pullImage
|
|
267
|
+
if (options.pullImage) Underpost.cluster.pullImage('mariadb:latest', options);
|
|
276
268
|
shellExec(`kubectl apply -f ${underpostRoot}/manifests/mariadb/storage-class.yaml -n ${options.namespace}`);
|
|
277
269
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mariadb -n ${options.namespace}`);
|
|
278
270
|
}
|
|
279
|
-
if (options.
|
|
271
|
+
if (options.mysql) {
|
|
280
272
|
shellExec(
|
|
281
273
|
`sudo kubectl create secret generic mysql-secret --from-file=username=/home/dd/engine/engine-private/mysql-username --from-file=password=/home/dd/engine/engine-private/mysql-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
|
|
282
274
|
);
|
|
@@ -285,15 +277,15 @@ EOF
|
|
|
285
277
|
shellExec(`sudo chown -R $(whoami):$(whoami) /mnt/data`);
|
|
286
278
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mysql -n ${options.namespace}`);
|
|
287
279
|
}
|
|
288
|
-
if (options.
|
|
289
|
-
if (options.pullImage
|
|
280
|
+
if (options.postgresql) {
|
|
281
|
+
if (options.pullImage) Underpost.cluster.pullImage('postgres:latest', options);
|
|
290
282
|
shellExec(
|
|
291
283
|
`sudo kubectl create secret generic postgres-secret --from-file=password=/home/dd/engine/engine-private/postgresql-password --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
|
|
292
284
|
);
|
|
293
285
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/postgresql -n ${options.namespace}`);
|
|
294
286
|
}
|
|
295
|
-
if (options.mongodb4
|
|
296
|
-
if (options.pullImage
|
|
287
|
+
if (options.mongodb4) {
|
|
288
|
+
if (options.pullImage) Underpost.cluster.pullImage('mongo:4.4', options);
|
|
297
289
|
shellExec(`kubectl apply -k ${underpostRoot}/manifests/mongodb-4.4 -n ${options.namespace}`);
|
|
298
290
|
|
|
299
291
|
const deploymentName = 'mongodb-deployment';
|
|
@@ -314,8 +306,8 @@ EOF
|
|
|
314
306
|
--eval 'rs.initiate(${JSON.stringify(mongoConfig)})'`,
|
|
315
307
|
);
|
|
316
308
|
}
|
|
317
|
-
} else if (options.
|
|
318
|
-
if (options.pullImage
|
|
309
|
+
} else if (options.mongodb) {
|
|
310
|
+
if (options.pullImage) Underpost.cluster.pullImage('mongo:latest', options);
|
|
319
311
|
shellExec(
|
|
320
312
|
`sudo kubectl create secret generic mongodb-keyfile --from-file=/home/dd/engine/engine-private/mongodb-keyfile --dry-run=client -o yaml | kubectl apply -f - -n ${options.namespace}`,
|
|
321
313
|
);
|
|
@@ -344,11 +336,11 @@ EOF
|
|
|
344
336
|
}
|
|
345
337
|
}
|
|
346
338
|
|
|
347
|
-
if (options.
|
|
339
|
+
if (options.contour) {
|
|
348
340
|
shellExec(
|
|
349
341
|
`kubectl apply -f https://cdn.jsdelivr.net/gh/projectcontour/contour@release-1.33/examples/render/contour.yaml`,
|
|
350
342
|
);
|
|
351
|
-
if (options.kubeadm
|
|
343
|
+
if (options.kubeadm) {
|
|
352
344
|
// Envoy service might need NodePort for kubeadm
|
|
353
345
|
shellExec(
|
|
354
346
|
`sudo kubectl apply -f ${underpostRoot}/manifests/envoy-service-nodeport.yaml -n ${options.namespace}`,
|
|
@@ -358,7 +350,7 @@ EOF
|
|
|
358
350
|
// so a specific NodePort service might not be needed or can be configured differently.
|
|
359
351
|
}
|
|
360
352
|
|
|
361
|
-
if (options.
|
|
353
|
+
if (options.certManager) {
|
|
362
354
|
if (!Underpost.deploy.get('cert-manager').find((p) => p.STATUS === 'Running')) {
|
|
363
355
|
shellExec(`helm repo add jetstack https://charts.jetstack.io --force-update`);
|
|
364
356
|
shellExec(
|
package/src/cli/index.js
CHANGED
|
@@ -218,7 +218,6 @@ program
|
|
|
218
218
|
.option('--contour', 'Initializes the cluster with Project Contour base HTTPProxy and Envoy.')
|
|
219
219
|
.option('--cert-manager', "Initializes the cluster with a Let's Encrypt production ClusterIssuer.")
|
|
220
220
|
.option('--dedicated-gpu', 'Initializes the cluster with dedicated GPU base resources and environment settings.')
|
|
221
|
-
.option('--full', 'Initializes the cluster with all available statefulsets and services.')
|
|
222
221
|
.option(
|
|
223
222
|
'--ns-use <ns-name>',
|
|
224
223
|
"Switches the current Kubernetes context to the specified namespace (creates if it doesn't exist).",
|
package/src/cli/run.js
CHANGED
|
@@ -217,6 +217,20 @@ class UnderpostRun {
|
|
|
217
217
|
}
|
|
218
218
|
},
|
|
219
219
|
|
|
220
|
+
/**
|
|
221
|
+
* @method ipfs-expose
|
|
222
|
+
* @description Exposes IPFS Cluster services on specified ports for local access.
|
|
223
|
+
* @type {Function}
|
|
224
|
+
* @memberof UnderpostRun
|
|
225
|
+
*/
|
|
226
|
+
'ipfs-expose': (path, options = DEFAULT_OPTION) => {
|
|
227
|
+
const ports = [5001, 9094, 8080];
|
|
228
|
+
for (const port of ports)
|
|
229
|
+
shellExec(`node bin deploy --expose ipfs-cluster --expose-port ${port} --disable-update-underpost-config`, {
|
|
230
|
+
async: true,
|
|
231
|
+
});
|
|
232
|
+
},
|
|
233
|
+
|
|
220
234
|
/**
|
|
221
235
|
* @method metadata
|
|
222
236
|
* @description Generates metadata for the specified path after exposing the development cluster.
|
|
@@ -27,8 +27,8 @@ const LoadingAnimation = {
|
|
|
27
27
|
? subThemeManager.darkColor
|
|
28
28
|
: `#66e400`
|
|
29
29
|
: subThemeManager.lightColor
|
|
30
|
-
|
|
31
|
-
|
|
30
|
+
? subThemeManager.lightColor
|
|
31
|
+
: `#157e00`};"
|
|
32
32
|
></div>
|
|
33
33
|
`,
|
|
34
34
|
);
|
|
@@ -131,7 +131,6 @@ const LoadingAnimation = {
|
|
|
131
131
|
if (!backgroundContainer) backgroundContainer = '.ssr-background';
|
|
132
132
|
if (s(backgroundContainer)) {
|
|
133
133
|
s(backgroundContainer).style.display = 'none';
|
|
134
|
-
if (s(`.main-body-btn-container`)) s(`.main-body-btn-container`).classList.remove('hide');
|
|
135
134
|
if (callBack) callBack();
|
|
136
135
|
}
|
|
137
136
|
},
|
|
@@ -322,7 +322,7 @@ const Modal = {
|
|
|
322
322
|
'body',
|
|
323
323
|
html`
|
|
324
324
|
<div
|
|
325
|
-
class="abs main-body-btn-container
|
|
325
|
+
class="abs main-body-btn-container"
|
|
326
326
|
style="top: ${options.heightTopBar + 50}px; z-index: 9; ${true ||
|
|
327
327
|
(options.mode && options.mode.match('right'))
|
|
328
328
|
? 'right'
|
package/src/index.js
CHANGED
|
@@ -99,7 +99,7 @@ class ExpressService {
|
|
|
99
99
|
|
|
100
100
|
if (origins && isDevProxyContext())
|
|
101
101
|
origins.push(devProxyHostFactory({ host, includeHttp: true, tls: isTlsDevProxy() }));
|
|
102
|
-
app.set('trust proxy',
|
|
102
|
+
app.set('trust proxy', 1);
|
|
103
103
|
|
|
104
104
|
app.use((req, res, next) => {
|
|
105
105
|
res.on('finish', () => {
|
package/src/server/auth.js
CHANGED
|
@@ -647,24 +647,24 @@ function applySecurity(app, opts = {}) {
|
|
|
647
647
|
}),
|
|
648
648
|
);
|
|
649
649
|
logger.info('Cors origin', origin);
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
650
|
+
if (!process.env.DISABLE_API_RATE_LIMIT) {
|
|
651
|
+
// Rate limiting + slow down
|
|
652
|
+
const limiter = rateLimit({
|
|
653
|
+
windowMs: rate.windowMs,
|
|
654
|
+
max: rate.max,
|
|
655
|
+
standardHeaders: true,
|
|
656
|
+
legacyHeaders: false,
|
|
657
|
+
message: { error: 'Too many requests, please try again later.' },
|
|
658
|
+
});
|
|
659
|
+
app.use(limiter);
|
|
660
|
+
|
|
661
|
+
const speedLimiter = slowDown({
|
|
662
|
+
windowMs: slowdown.windowMs,
|
|
663
|
+
delayAfter: slowdown.delayAfter,
|
|
664
|
+
delayMs: () => slowdown.delayMs,
|
|
665
|
+
});
|
|
666
|
+
app.use(speedLimiter);
|
|
667
|
+
}
|
|
668
668
|
// Cookie parsing
|
|
669
669
|
app.use(cookieParser(process.env.JWT_SECRET));
|
|
670
670
|
}
|
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lightweight IPFS HTTP client for communicating with a Kubo (go-ipfs) node
|
|
3
|
+
* and an IPFS Cluster daemon running side-by-side in the same StatefulSet.
|
|
4
|
+
*
|
|
5
|
+
* Kubo API (port 5001) – add / pin / cat content.
|
|
6
|
+
* Cluster API (port 9094) – replicate pins across the cluster.
|
|
7
|
+
*
|
|
8
|
+
* Uses native `FormData` + `Blob` (Node ≥ 18) for reliable multipart encoding.
|
|
9
|
+
*
|
|
10
|
+
* @module src/server/ipfs-client.js
|
|
11
|
+
* @namespace IpfsClient
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import stringify from 'fast-json-stable-stringify';
|
|
15
|
+
import { loggerFactory } from './logger.js';
|
|
16
|
+
|
|
17
|
+
const logger = loggerFactory(import.meta);
|
|
18
|
+
|
|
19
|
+
// ─────────────────────────────────────────────────────────
|
|
20
|
+
// URL helpers
|
|
21
|
+
// ─────────────────────────────────────────────────────────
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Base URL of the Kubo RPC API (port 5001).
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
const getIpfsApiUrl = () =>
|
|
28
|
+
process.env.IPFS_API_URL || `http://${process.env.NODE_ENV === 'development' ? 'localhost' : 'ipfs-cluster'}:5001`;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Base URL of the IPFS Cluster REST API (port 9094).
|
|
32
|
+
* @returns {string}
|
|
33
|
+
*/
|
|
34
|
+
const getClusterApiUrl = () =>
|
|
35
|
+
process.env.IPFS_CLUSTER_API_URL ||
|
|
36
|
+
`http://${process.env.NODE_ENV === 'development' ? 'localhost' : 'ipfs-cluster'}:9094`;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Base URL of the IPFS HTTP Gateway (port 8080).
|
|
40
|
+
* @returns {string}
|
|
41
|
+
*/
|
|
42
|
+
const getGatewayUrl = () =>
|
|
43
|
+
process.env.IPFS_GATEWAY_URL ||
|
|
44
|
+
`http://${process.env.NODE_ENV === 'development' ? 'localhost' : 'ipfs-cluster'}:8080`;
|
|
45
|
+
|
|
46
|
+
// ─────────────────────────────────────────────────────────
|
|
47
|
+
// Core: add content
|
|
48
|
+
// ─────────────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* @typedef {Object} IpfsAddResult
|
|
52
|
+
* @property {string} cid – CID (Content Identifier) returned by the node.
|
|
53
|
+
* @property {number} size – Cumulative DAG size reported by the node.
|
|
54
|
+
*/
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Add arbitrary bytes to the Kubo node AND pin them on the IPFS Cluster.
|
|
58
|
+
*
|
|
59
|
+
* 1. `POST /api/v0/add?pin=true` to Kubo (5001) – stores + locally pins.
|
|
60
|
+
* 2. `POST /pins/<CID>` to the Cluster REST API (9094) – replicates the pin
|
|
61
|
+
* across every peer so `GET /pins` on the cluster shows the content.
|
|
62
|
+
* 3. Copies into MFS so the Web UI "Files" section shows the file.
|
|
63
|
+
*
|
|
64
|
+
* @param {Buffer|string} content – raw bytes or a UTF-8 string to store.
|
|
65
|
+
* @param {string} [filename='data'] – logical filename for the upload.
|
|
66
|
+
* @param {string} [mfsPath] – optional full MFS path.
|
|
67
|
+
* When omitted defaults to `/pinned/<filename>`.
|
|
68
|
+
* @returns {Promise<IpfsAddResult|null>} `null` when the node is unreachable.
|
|
69
|
+
*/
|
|
70
|
+
const addToIpfs = async (content, filename = 'data', mfsPath) => {
|
|
71
|
+
const kuboUrl = getIpfsApiUrl();
|
|
72
|
+
const clusterUrl = getClusterApiUrl();
|
|
73
|
+
|
|
74
|
+
// Build multipart body using native FormData + Blob (Node ≥ 18).
|
|
75
|
+
const buf = Buffer.isBuffer(content) ? content : Buffer.from(content, 'utf-8');
|
|
76
|
+
const formData = new FormData();
|
|
77
|
+
formData.append('file', new Blob([buf]), filename);
|
|
78
|
+
|
|
79
|
+
// ── Step 1: add to Kubo ──────────────────────────────
|
|
80
|
+
let cid;
|
|
81
|
+
let size;
|
|
82
|
+
try {
|
|
83
|
+
const res = await fetch(`${kuboUrl}/api/v0/add?pin=true&cid-version=1`, {
|
|
84
|
+
method: 'POST',
|
|
85
|
+
body: formData,
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (!res.ok) {
|
|
89
|
+
const text = await res.text();
|
|
90
|
+
logger.error(`IPFS Kubo add failed (${res.status}): ${text}`);
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const json = await res.json();
|
|
95
|
+
cid = json.Hash;
|
|
96
|
+
size = Number(json.Size);
|
|
97
|
+
logger.info(`IPFS Kubo add OK – CID: ${cid}, size: ${size}`);
|
|
98
|
+
} catch (err) {
|
|
99
|
+
logger.warn(`IPFS Kubo node unreachable at ${kuboUrl}: ${err.message}`);
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// ── Step 2: pin to the Cluster ───────────────────────
|
|
104
|
+
try {
|
|
105
|
+
const clusterRes = await fetch(`${clusterUrl}/pins/${encodeURIComponent(cid)}`, {
|
|
106
|
+
method: 'POST',
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
if (!clusterRes.ok) {
|
|
110
|
+
const text = await clusterRes.text();
|
|
111
|
+
logger.warn(`IPFS Cluster pin failed (${clusterRes.status}): ${text}`);
|
|
112
|
+
} else {
|
|
113
|
+
logger.info(`IPFS Cluster pin OK – CID: ${cid}`);
|
|
114
|
+
}
|
|
115
|
+
} catch (err) {
|
|
116
|
+
logger.warn(`IPFS Cluster unreachable at ${clusterUrl}: ${err.message}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Step 3: copy into MFS so the Web UI "Files" section shows it ─
|
|
120
|
+
const destPath = mfsPath || `/pinned/${filename}`;
|
|
121
|
+
const destDir = destPath.substring(0, destPath.lastIndexOf('/')) || '/';
|
|
122
|
+
try {
|
|
123
|
+
// Ensure parent directory exists in MFS
|
|
124
|
+
await fetch(`${kuboUrl}/api/v0/files/mkdir?arg=${encodeURIComponent(destDir)}&parents=true`, { method: 'POST' });
|
|
125
|
+
|
|
126
|
+
// Remove existing entry if present (cp fails on duplicates)
|
|
127
|
+
await fetch(`${kuboUrl}/api/v0/files/rm?arg=${encodeURIComponent(destPath)}&force=true`, {
|
|
128
|
+
method: 'POST',
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Copy the CID into MFS
|
|
132
|
+
const cpRes = await fetch(
|
|
133
|
+
`${kuboUrl}/api/v0/files/cp?arg=/ipfs/${encodeURIComponent(cid)}&arg=${encodeURIComponent(destPath)}`,
|
|
134
|
+
{ method: 'POST' },
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (!cpRes.ok) {
|
|
138
|
+
const text = await cpRes.text();
|
|
139
|
+
logger.warn(`IPFS MFS cp failed (${cpRes.status}): ${text}`);
|
|
140
|
+
} else {
|
|
141
|
+
logger.info(`IPFS MFS cp OK – ${destPath} → ${cid}`);
|
|
142
|
+
}
|
|
143
|
+
} catch (err) {
|
|
144
|
+
logger.warn(`IPFS MFS cp unreachable: ${err.message}`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return { cid, size };
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// ─────────────────────────────────────────────────────────
|
|
151
|
+
// Convenience wrappers
|
|
152
|
+
// ─────────────────────────────────────────────────────────
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* Add a JSON-serialisable object to IPFS.
|
|
156
|
+
*
|
|
157
|
+
* @param {any} obj – value to serialise.
|
|
158
|
+
* @param {string} [filename='data.json']
|
|
159
|
+
* @param {string} [mfsPath] – optional full MFS destination path.
|
|
160
|
+
* @returns {Promise<IpfsAddResult|null>}
|
|
161
|
+
*/
|
|
162
|
+
const addJsonToIpfs = async (obj, filename = 'data.json', mfsPath) => {
|
|
163
|
+
const payload = stringify(obj);
|
|
164
|
+
return addToIpfs(Buffer.from(payload, 'utf-8'), filename, mfsPath);
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Add a binary buffer (e.g. a PNG image) to IPFS.
|
|
169
|
+
*
|
|
170
|
+
* @param {Buffer} buffer – raw image / file bytes.
|
|
171
|
+
* @param {string} filename – e.g. `"atlas.png"`.
|
|
172
|
+
* @param {string} [mfsPath] – optional full MFS destination path.
|
|
173
|
+
* @returns {Promise<IpfsAddResult|null>}
|
|
174
|
+
*/
|
|
175
|
+
const addBufferToIpfs = async (buffer, filename, mfsPath) => {
|
|
176
|
+
return addToIpfs(buffer, filename, mfsPath);
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
// ─────────────────────────────────────────────────────────
|
|
180
|
+
// Pin management
|
|
181
|
+
// ─────────────────────────────────────────────────────────
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Explicitly pin an existing CID on both the Kubo node and the Cluster.
|
|
185
|
+
*
|
|
186
|
+
* @param {string} cid
|
|
187
|
+
* @param {string} [type='recursive'] – `'recursive'` | `'direct'`
|
|
188
|
+
* @returns {Promise<boolean>} `true` when at least the Kubo pin succeeded.
|
|
189
|
+
*/
|
|
190
|
+
const pinCid = async (cid, type = 'recursive') => {
|
|
191
|
+
const kuboUrl = getIpfsApiUrl();
|
|
192
|
+
const clusterUrl = getClusterApiUrl();
|
|
193
|
+
let kuboOk = false;
|
|
194
|
+
|
|
195
|
+
// Kubo pin
|
|
196
|
+
try {
|
|
197
|
+
const res = await fetch(`${kuboUrl}/api/v0/pin/add?arg=${encodeURIComponent(cid)}&type=${type}`, {
|
|
198
|
+
method: 'POST',
|
|
199
|
+
});
|
|
200
|
+
if (!res.ok) {
|
|
201
|
+
const text = await res.text();
|
|
202
|
+
logger.error(`IPFS Kubo pin/add failed (${res.status}): ${text}`);
|
|
203
|
+
} else {
|
|
204
|
+
kuboOk = true;
|
|
205
|
+
logger.info(`IPFS Kubo pin OK – CID: ${cid} (${type})`);
|
|
206
|
+
}
|
|
207
|
+
} catch (err) {
|
|
208
|
+
logger.warn(`IPFS Kubo pin unreachable: ${err.message}`);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Cluster pin
|
|
212
|
+
try {
|
|
213
|
+
const clusterRes = await fetch(`${clusterUrl}/pins/${encodeURIComponent(cid)}`, {
|
|
214
|
+
method: 'POST',
|
|
215
|
+
});
|
|
216
|
+
if (!clusterRes.ok) {
|
|
217
|
+
const text = await clusterRes.text();
|
|
218
|
+
logger.warn(`IPFS Cluster pin failed (${clusterRes.status}): ${text}`);
|
|
219
|
+
} else {
|
|
220
|
+
logger.info(`IPFS Cluster pin OK – CID: ${cid}`);
|
|
221
|
+
}
|
|
222
|
+
} catch (err) {
|
|
223
|
+
logger.warn(`IPFS Cluster pin unreachable: ${err.message}`);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return kuboOk;
|
|
227
|
+
};
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Unpin a CID from both the Kubo node and the Cluster.
|
|
231
|
+
*
|
|
232
|
+
* @param {string} cid
|
|
233
|
+
* @returns {Promise<boolean>}
|
|
234
|
+
*/
|
|
235
|
+
const unpinCid = async (cid) => {
|
|
236
|
+
const kuboUrl = getIpfsApiUrl();
|
|
237
|
+
const clusterUrl = getClusterApiUrl();
|
|
238
|
+
let kuboOk = false;
|
|
239
|
+
|
|
240
|
+
// Cluster unpin
|
|
241
|
+
try {
|
|
242
|
+
const clusterRes = await fetch(`${clusterUrl}/pins/${encodeURIComponent(cid)}`, {
|
|
243
|
+
method: 'DELETE',
|
|
244
|
+
});
|
|
245
|
+
if (!clusterRes.ok) {
|
|
246
|
+
const text = await clusterRes.text();
|
|
247
|
+
logger.warn(`IPFS Cluster unpin failed (${clusterRes.status}): ${text}`);
|
|
248
|
+
} else {
|
|
249
|
+
logger.info(`IPFS Cluster unpin OK – CID: ${cid}`);
|
|
250
|
+
}
|
|
251
|
+
} catch (err) {
|
|
252
|
+
logger.warn(`IPFS Cluster unpin unreachable: ${err.message}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Kubo unpin
|
|
256
|
+
try {
|
|
257
|
+
const res = await fetch(`${kuboUrl}/api/v0/pin/rm?arg=${encodeURIComponent(cid)}`, {
|
|
258
|
+
method: 'POST',
|
|
259
|
+
});
|
|
260
|
+
if (!res.ok) {
|
|
261
|
+
const text = await res.text();
|
|
262
|
+
// "not pinned or pinned indirectly" means the CID is already unpinned – treat as success
|
|
263
|
+
if (text.includes('not pinned')) {
|
|
264
|
+
kuboOk = true;
|
|
265
|
+
logger.info(`IPFS Kubo unpin – CID already not pinned: ${cid}`);
|
|
266
|
+
} else {
|
|
267
|
+
logger.warn(`IPFS Kubo pin/rm failed (${res.status}): ${text}`);
|
|
268
|
+
}
|
|
269
|
+
} else {
|
|
270
|
+
kuboOk = true;
|
|
271
|
+
logger.info(`IPFS Kubo unpin OK – CID: ${cid}`);
|
|
272
|
+
}
|
|
273
|
+
} catch (err) {
|
|
274
|
+
logger.warn(`IPFS Kubo unpin unreachable: ${err.message}`);
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return kuboOk;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
// ─────────────────────────────────────────────────────────
|
|
281
|
+
// Retrieval
|
|
282
|
+
// ─────────────────────────────────────────────────────────
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Retrieve raw bytes for a CID from the IPFS HTTP Gateway (port 8080).
|
|
286
|
+
*
|
|
287
|
+
* @param {string} cid
|
|
288
|
+
* @returns {Promise<Buffer|null>}
|
|
289
|
+
*/
|
|
290
|
+
const getFromIpfs = async (cid) => {
|
|
291
|
+
const url = getGatewayUrl();
|
|
292
|
+
try {
|
|
293
|
+
const res = await fetch(`${url}/ipfs/${encodeURIComponent(cid)}`);
|
|
294
|
+
if (!res.ok) {
|
|
295
|
+
logger.error(`IPFS gateway GET failed (${res.status}) for ${cid}`);
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
const arrayBuffer = await res.arrayBuffer();
|
|
299
|
+
return Buffer.from(arrayBuffer);
|
|
300
|
+
} catch (err) {
|
|
301
|
+
logger.warn(`IPFS gateway unreachable at ${url}: ${err.message}`);
|
|
302
|
+
return null;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
|
|
306
|
+
// ─────────────────────────────────────────────────────────
|
|
307
|
+
// Diagnostics
|
|
308
|
+
// ─────────────────────────────────────────────────────────
|
|
309
|
+
|
|
310
|
+
/**
|
|
311
|
+
* List all pins tracked by the IPFS Cluster (port 9094).
|
|
312
|
+
* Each line in the response is a JSON object with at least a `cid` field.
|
|
313
|
+
*
|
|
314
|
+
* @returns {Promise<Array<{ cid: string, name: string, peer_map: object }>>}
|
|
315
|
+
*/
|
|
316
|
+
const listClusterPins = async () => {
|
|
317
|
+
const clusterUrl = getClusterApiUrl();
|
|
318
|
+
try {
|
|
319
|
+
const res = await fetch(`${clusterUrl}/pins`);
|
|
320
|
+
if (res.status === 204) {
|
|
321
|
+
// 204 No Content → the cluster has no pins at all.
|
|
322
|
+
return [];
|
|
323
|
+
}
|
|
324
|
+
if (!res.ok) {
|
|
325
|
+
const text = await res.text();
|
|
326
|
+
logger.error(`IPFS Cluster list pins failed (${res.status}): ${text}`);
|
|
327
|
+
return [];
|
|
328
|
+
}
|
|
329
|
+
const text = await res.text();
|
|
330
|
+
// The cluster streams one JSON object per line (NDJSON).
|
|
331
|
+
return text
|
|
332
|
+
.split('\n')
|
|
333
|
+
.filter((line) => line.trim())
|
|
334
|
+
.map((line) => {
|
|
335
|
+
try {
|
|
336
|
+
return JSON.parse(line);
|
|
337
|
+
} catch {
|
|
338
|
+
return null;
|
|
339
|
+
}
|
|
340
|
+
})
|
|
341
|
+
.filter(Boolean);
|
|
342
|
+
} catch (err) {
|
|
343
|
+
logger.warn(`IPFS Cluster unreachable at ${clusterUrl}: ${err.message}`);
|
|
344
|
+
return [];
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* List pins tracked by the local Kubo node (port 5001).
|
|
350
|
+
*
|
|
351
|
+
* @param {string} [type='recursive'] – `'all'` | `'recursive'` | `'direct'` | `'indirect'`
|
|
352
|
+
* @returns {Promise<Object<string, { Type: string }>>} Map of CID → pin info.
|
|
353
|
+
*/
|
|
354
|
+
const listKuboPins = async (type = 'recursive') => {
|
|
355
|
+
const kuboUrl = getIpfsApiUrl();
|
|
356
|
+
try {
|
|
357
|
+
const res = await fetch(`${kuboUrl}/api/v0/pin/ls?type=${type}`, {
|
|
358
|
+
method: 'POST',
|
|
359
|
+
});
|
|
360
|
+
if (!res.ok) {
|
|
361
|
+
const text = await res.text();
|
|
362
|
+
logger.error(`IPFS Kubo pin/ls failed (${res.status}): ${text}`);
|
|
363
|
+
return {};
|
|
364
|
+
}
|
|
365
|
+
const json = await res.json();
|
|
366
|
+
return json.Keys || {};
|
|
367
|
+
} catch (err) {
|
|
368
|
+
logger.warn(`IPFS Kubo pin/ls unreachable: ${err.message}`);
|
|
369
|
+
return {};
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
// ─────────────────────────────────────────────────────────
|
|
374
|
+
// MFS management
|
|
375
|
+
// ─────────────────────────────────────────────────────────
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Remove a file or directory from the Kubo MFS (Mutable File System).
|
|
379
|
+
* This cleans up entries visible in the IPFS Web UI "Files" section.
|
|
380
|
+
*
|
|
381
|
+
* @param {string} mfsPath – Full MFS path to remove, e.g. `/pinned/myfile.json`
|
|
382
|
+
* or `/object-layer/itemId`.
|
|
383
|
+
* @param {boolean} [recursive=true] – When `true`, removes directories recursively.
|
|
384
|
+
* @returns {Promise<boolean>} `true` when the removal succeeded or the path didn't exist.
|
|
385
|
+
*/
|
|
386
|
+
const removeMfsPath = async (mfsPath, recursive = true) => {
|
|
387
|
+
const kuboUrl = getIpfsApiUrl();
|
|
388
|
+
try {
|
|
389
|
+
// First check if the path exists via stat; if it doesn't we can return early.
|
|
390
|
+
const statRes = await fetch(`${kuboUrl}/api/v0/files/stat?arg=${encodeURIComponent(mfsPath)}`, { method: 'POST' });
|
|
391
|
+
if (!statRes.ok) {
|
|
392
|
+
// Path doesn't exist – nothing to remove.
|
|
393
|
+
logger.info(`IPFS MFS rm – path does not exist, skipping: ${mfsPath}`);
|
|
394
|
+
return true;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const rmRes = await fetch(
|
|
398
|
+
`${kuboUrl}/api/v0/files/rm?arg=${encodeURIComponent(mfsPath)}&force=true${recursive ? '&recursive=true' : ''}`,
|
|
399
|
+
{ method: 'POST' },
|
|
400
|
+
);
|
|
401
|
+
if (!rmRes.ok) {
|
|
402
|
+
const text = await rmRes.text();
|
|
403
|
+
logger.warn(`IPFS MFS rm failed (${rmRes.status}): ${text}`);
|
|
404
|
+
return false;
|
|
405
|
+
}
|
|
406
|
+
logger.info(`IPFS MFS rm OK – ${mfsPath}`);
|
|
407
|
+
return true;
|
|
408
|
+
} catch (err) {
|
|
409
|
+
logger.warn(`IPFS MFS rm unreachable: ${err.message}`);
|
|
410
|
+
return false;
|
|
411
|
+
}
|
|
412
|
+
};
|
|
413
|
+
|
|
414
|
+
// ─────────────────────────────────────────────────────────
|
|
415
|
+
// Export
|
|
416
|
+
// ─────────────────────────────────────────────────────────
|
|
417
|
+
|
|
418
|
+
const IpfsClient = {
|
|
419
|
+
getIpfsApiUrl,
|
|
420
|
+
getClusterApiUrl,
|
|
421
|
+
getGatewayUrl,
|
|
422
|
+
addToIpfs,
|
|
423
|
+
addJsonToIpfs,
|
|
424
|
+
addBufferToIpfs,
|
|
425
|
+
pinCid,
|
|
426
|
+
unpinCid,
|
|
427
|
+
getFromIpfs,
|
|
428
|
+
listClusterPins,
|
|
429
|
+
listKuboPins,
|
|
430
|
+
removeMfsPath,
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
export { IpfsClient };
|
package/bin/ssl.js
DELETED
|
@@ -1,63 +0,0 @@
|
|
|
1
|
-
import fs from 'fs';
|
|
2
|
-
import read from 'read';
|
|
3
|
-
import dotenv from 'dotenv';
|
|
4
|
-
import { getRootDirectory, pbcopy, shellExec } from '../src/server/process.js';
|
|
5
|
-
import { loggerFactory } from '../src/server/logger.js';
|
|
6
|
-
import { Cmd, loadConf } from '../src/server/conf.js';
|
|
7
|
-
import { buildSSL } from '../src/server/ssl.js';
|
|
8
|
-
|
|
9
|
-
dotenv.config();
|
|
10
|
-
|
|
11
|
-
const logger = loggerFactory(import.meta);
|
|
12
|
-
|
|
13
|
-
await logger.setUpInfo();
|
|
14
|
-
|
|
15
|
-
const [exe, dir, deployId, hosts] = process.argv;
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
let cmd;
|
|
19
|
-
await loadConf(deployId);
|
|
20
|
-
shellExec(Cmd.conf(deployId));
|
|
21
|
-
const confServer = JSON.parse(fs.readFileSync(`./conf/conf.server.json`, 'utf8'));
|
|
22
|
-
for (const host of hosts.split(',')) {
|
|
23
|
-
if (host in confServer) {
|
|
24
|
-
const directory = confServer[host]['/']?.['directory'] ? confServer[host]['/']['directory'] : undefined;
|
|
25
|
-
cmd = `sudo certbot certonly --webroot --webroot-path ${
|
|
26
|
-
directory ? directory : `${getRootDirectory()}/public/${host}`
|
|
27
|
-
} --cert-name ${host} -d ${host}`;
|
|
28
|
-
// directory ? directory : `${getRootDirectory()}/public/${host}`
|
|
29
|
-
// directory ? directory : `${getRootDirectory()}/public/www.${host.split('.').slice(-2).join('.')}`
|
|
30
|
-
|
|
31
|
-
// You can get multi domain cert by specifying (extra) -d
|
|
32
|
-
// For example
|
|
33
|
-
// certbot -d example.com -d example.net -d www.example.org
|
|
34
|
-
|
|
35
|
-
// delete all file (no increment live folder)
|
|
36
|
-
// certbot delete --cert-name <domain>
|
|
37
|
-
|
|
38
|
-
logger.info(`Run the following command`, cmd);
|
|
39
|
-
try {
|
|
40
|
-
await pbcopy(cmd);
|
|
41
|
-
await read({ prompt: 'Command copy to clipboard, press enter to continue.\n' });
|
|
42
|
-
} catch (error) {
|
|
43
|
-
logger.error(error);
|
|
44
|
-
}
|
|
45
|
-
// Certificate
|
|
46
|
-
if (process.argv.includes('build')) await buildSSL(host);
|
|
47
|
-
logger.info('Certificate saved', host);
|
|
48
|
-
} else throw new Error(`host not found: ${host}`);
|
|
49
|
-
}
|
|
50
|
-
// check for renewal conf:
|
|
51
|
-
// /etc/letsencrypt/renewal
|
|
52
|
-
// /etc/letsencrypt/live
|
|
53
|
-
cmd = `sudo certbot renew --dry-run`;
|
|
54
|
-
try {
|
|
55
|
-
await pbcopy(cmd);
|
|
56
|
-
} catch (error) {
|
|
57
|
-
logger.error(error);
|
|
58
|
-
}
|
|
59
|
-
logger.info(`run the following command for renewal. Command copy to clipboard`, cmd);
|
|
60
|
-
logger.info(`success install SLL`, hosts);
|
|
61
|
-
} catch (error) {
|
|
62
|
-
logger.error(error, error.stack);
|
|
63
|
-
}
|