underpost 2.8.885 → 2.81.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/.env.production +3 -0
- package/.github/workflows/ghpkg.ci.yml +1 -1
- package/.github/workflows/npmpkg.ci.yml +1 -1
- package/.github/workflows/publish.ci.yml +5 -5
- package/.github/workflows/pwa-microservices-template-page.cd.yml +1 -1
- package/.github/workflows/pwa-microservices-template-test.ci.yml +1 -1
- package/.vscode/zed.keymap.json +17 -0
- package/.vscode/zed.settings.json +20 -0
- package/CHANGELOG.md +145 -1
- package/Dockerfile +20 -3
- package/README.md +6 -6
- package/bin/build.js +18 -9
- package/bin/deploy.js +130 -195
- package/bin/zed.js +20 -0
- package/cli.md +13 -7
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +50 -50
- package/manifests/deployment/dd-test-development/proxy.yaml +4 -4
- package/manifests/lxd/underpost-setup.sh +5 -5
- package/package.json +3 -4
- package/{manifests/maas → scripts}/ssh-cluster-info.sh +1 -1
- package/scripts/ssl.sh +164 -0
- package/src/cli/baremetal.js +8 -8
- package/src/cli/cloud-init.js +1 -1
- package/src/cli/cluster.js +15 -4
- package/src/cli/cron.js +1 -1
- package/src/cli/db.js +2 -1
- package/src/cli/deploy.js +65 -14
- package/src/cli/fs.js +2 -2
- package/src/cli/image.js +19 -2
- package/src/cli/index.js +11 -4
- package/src/cli/monitor.js +34 -1
- package/src/cli/repository.js +42 -1
- package/src/cli/run.js +396 -86
- package/src/cli/script.js +32 -0
- package/src/cli/secrets.js +34 -0
- package/src/cli/test.js +42 -1
- package/src/client/components/core/Css.js +0 -8
- package/src/client/components/core/windowGetDimensions.js +229 -162
- package/src/index.js +2 -2
- package/src/mailer/MailerProvider.js +1 -0
- package/src/runtime/express/Dockerfile +41 -0
- package/src/runtime/express/Express.js +12 -4
- package/src/runtime/lampp/Dockerfile +1 -1
- package/src/server/backup.js +20 -0
- package/src/server/client-build-live.js +12 -10
- package/src/server/client-build.js +136 -91
- package/src/server/client-dev-server.js +16 -2
- package/src/server/client-icons.js +19 -0
- package/src/server/conf.js +495 -69
- package/src/server/dns.js +169 -46
- package/src/server/downloader.js +65 -24
- package/src/server/object-layer.js +260 -162
- package/src/server/peer.js +2 -8
- package/src/server/proxy.js +93 -76
- package/src/server/runtime.js +15 -16
- package/src/server/ssr.js +4 -4
- package/src/server/tls.js +251 -0
- package/src/server/valkey.js +11 -10
- package/src/ws/IoInterface.js +2 -1
- package/src/ws/IoServer.js +2 -1
- package/src/ws/core/core.ws.connection.js +1 -1
- package/src/ws/core/core.ws.emit.js +1 -1
- package/src/ws/core/core.ws.server.js +1 -1
- package/manifests/maas/lxd-preseed.yaml +0 -32
- package/src/server/ssl.js +0 -108
- /package/{manifests/maas → scripts}/device-scan.sh +0 -0
- /package/{manifests/maas → scripts}/gpu-diag.sh +0 -0
- /package/{manifests/maas → scripts}/maas-setup.sh +0 -0
- /package/{manifests/maas → scripts}/nat-iptables.sh +0 -0
- /package/{manifests/maas → scripts}/nvim.sh +0 -0
- /package/{manifests/maas → scripts}/snap-clean.sh +0 -0
|
@@ -13,7 +13,7 @@ spec:
|
|
|
13
13
|
enableWebsockets: true
|
|
14
14
|
services:
|
|
15
15
|
- name: dd-test-development-blue-service
|
|
16
|
-
port:
|
|
16
|
+
port: 4041
|
|
17
17
|
weight: 100
|
|
18
18
|
|
|
19
19
|
- conditions:
|
|
@@ -21,7 +21,7 @@ spec:
|
|
|
21
21
|
enableWebsockets: true
|
|
22
22
|
services:
|
|
23
23
|
- name: dd-test-development-blue-service
|
|
24
|
-
port:
|
|
24
|
+
port: 4042
|
|
25
25
|
weight: 100
|
|
26
26
|
|
|
27
27
|
---
|
|
@@ -38,7 +38,7 @@ spec:
|
|
|
38
38
|
enableWebsockets: true
|
|
39
39
|
services:
|
|
40
40
|
- name: dd-test-development-blue-service
|
|
41
|
-
port:
|
|
41
|
+
port: 4043
|
|
42
42
|
weight: 100
|
|
43
43
|
|
|
44
44
|
- conditions:
|
|
@@ -46,6 +46,6 @@ spec:
|
|
|
46
46
|
enableWebsockets: true
|
|
47
47
|
services:
|
|
48
48
|
- name: dd-test-development-blue-service
|
|
49
|
-
port:
|
|
49
|
+
port: 4044
|
|
50
50
|
weight: 100
|
|
51
51
|
|
|
@@ -39,15 +39,15 @@ sudo dnf install -y tar bzip2 git epel-release
|
|
|
39
39
|
sudo dnf -y update
|
|
40
40
|
|
|
41
41
|
# --- NVM and Node.js Installation ---
|
|
42
|
-
echo "Installing NVM and Node.js
|
|
42
|
+
echo "Installing NVM and Node.js v24.10.0..."
|
|
43
43
|
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash
|
|
44
44
|
|
|
45
45
|
# Load nvm for the current session
|
|
46
46
|
export NVM_DIR="$([ -z "${XDG_CONFIG_HOME-}" ] && printf %s "${HOME}/.nvm" || printf %s "${XDG_CONFIG_HOME}/nvm")"
|
|
47
47
|
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
|
|
48
48
|
|
|
49
|
-
nvm install
|
|
50
|
-
nvm use
|
|
49
|
+
nvm install 24.10.0
|
|
50
|
+
nvm use 24.10.0
|
|
51
51
|
|
|
52
52
|
echo "
|
|
53
53
|
██╗░░░██╗███╗░░██╗██████╗░███████╗██████╗░██████╗░░█████╗░░██████╗████████╗
|
|
@@ -65,9 +65,9 @@ npm install -g underpost
|
|
|
65
65
|
|
|
66
66
|
# Ensure underpost executable is in PATH and has execute permissions
|
|
67
67
|
# Adjusting this for global npm install which usually handles permissions
|
|
68
|
-
# If you still face issues, ensure /root/.nvm/versions/node/
|
|
68
|
+
# If you still face issues, ensure /root/.nvm/versions/node/v24.10.0/bin is in your PATH
|
|
69
69
|
# For global installs, it's usually handled automatically.
|
|
70
|
-
# chmod +x /root/.nvm/versions/node/
|
|
70
|
+
# chmod +x /root/.nvm/versions/node/v24.10.0/bin/underpost # This might not be necessary for global npm installs
|
|
71
71
|
|
|
72
72
|
# --- Kernel Module for Bridge Filtering ---
|
|
73
73
|
# This is crucial for Kubernetes networking (CNI)
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"main": "src/index.js",
|
|
4
4
|
"name": "underpost",
|
|
5
|
-
"version": "2.
|
|
5
|
+
"version": "2.81.0",
|
|
6
6
|
"description": "pwa api rest template",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"start": "env-cmd -f .env.production node --max-old-space-size=8192 src/server",
|
|
@@ -80,7 +80,7 @@
|
|
|
80
80
|
"http-proxy-middleware": "^2.0.6",
|
|
81
81
|
"ignore-walk": "^6.0.4",
|
|
82
82
|
"iovalkey": "^0.2.1",
|
|
83
|
-
"jimp": "^
|
|
83
|
+
"jimp": "^1.6.0",
|
|
84
84
|
"json-colorizer": "^2.2.2",
|
|
85
85
|
"jsonwebtoken": "^9.0.2",
|
|
86
86
|
"mariadb": "^3.2.2",
|
|
@@ -88,12 +88,11 @@
|
|
|
88
88
|
"mocha": "^10.8.2",
|
|
89
89
|
"mongoose": "^8.9.5",
|
|
90
90
|
"morgan": "^1.10.0",
|
|
91
|
-
"nodemailer": "^
|
|
91
|
+
"nodemailer": "^7.0.9",
|
|
92
92
|
"nodemon": "^3.0.1",
|
|
93
93
|
"peer": "^1.0.2",
|
|
94
94
|
"peerjs": "^1.5.2",
|
|
95
95
|
"prom-client": "^15.1.2",
|
|
96
|
-
"public-ip": "^6.0.1",
|
|
97
96
|
"read": "^2.1.0",
|
|
98
97
|
"rrule": "^2.8.1",
|
|
99
98
|
"sharp": "^0.32.5",
|
package/scripts/ssl.sh
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# RHEL / Rocky Linux — Generate local TLS cert + fullchain (cert + root CA) using mkcert
|
|
3
|
+
# Usage:
|
|
4
|
+
# ./generate-local-ssl-fullchain.sh /path/to/target "localhost 127.0.0.1 ::1"
|
|
5
|
+
|
|
6
|
+
set -euo pipefail
|
|
7
|
+
IFS=$'\n\t'
|
|
8
|
+
|
|
9
|
+
err() { echo "[ERROR] $*" >&2; }
|
|
10
|
+
info() { echo "[INFO] $*"; }
|
|
11
|
+
|
|
12
|
+
print_usage() {
|
|
13
|
+
cat <<'EOF'
|
|
14
|
+
Usage: $0 TARGET_DIR "domain1 domain2 ..."
|
|
15
|
+
Example: $0 /etc/ssl/local "localhost 127.0.0.1 ::1"
|
|
16
|
+
|
|
17
|
+
Outputs created in TARGET_DIR:
|
|
18
|
+
- <name>.pem : leaf/server certificate
|
|
19
|
+
- <name>-key.pem : private key
|
|
20
|
+
- <name>-fullchain.pem: certificate followed by root CA (use for servers needing full chain)
|
|
21
|
+
- rootCA.pem : local root CA (if OpenSSL fallback used or copied from mkcert CAROOT)
|
|
22
|
+
EOF
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if [[ ${1-} == "-h" || ${1-} == "--help" ]]; then
|
|
26
|
+
print_usage; exit 0; fi
|
|
27
|
+
|
|
28
|
+
TARGET_DIR="${1-}"
|
|
29
|
+
DOMAINS="${2-}"
|
|
30
|
+
|
|
31
|
+
if [[ -z "$TARGET_DIR" ]]; then read -rp "Target directory to store certificates (absolute path): " TARGET_DIR; fi
|
|
32
|
+
if [[ -z "$DOMAINS" ]]; then read -rp "Space-separated domains/IPs to include (e.g. 'localhost 127.0.0.1 api.myapp.test'): " DOMAINS; fi
|
|
33
|
+
|
|
34
|
+
TARGET_DIR="$(realpath -m "$TARGET_DIR")"
|
|
35
|
+
mkdir -p "$TARGET_DIR"
|
|
36
|
+
IFS=' ' read -r -a DOMAIN_ARR <<< "$DOMAINS"
|
|
37
|
+
if [[ ${#DOMAIN_ARR[@]} -eq 0 ]]; then err "No domains provided. Exiting."; exit 1; fi
|
|
38
|
+
|
|
39
|
+
NAME_SAFE="${DOMAIN_ARR[0]//[^a-zA-Z0-9_.-]/_}"
|
|
40
|
+
CERT_FILE="$TARGET_DIR/${NAME_SAFE}.pem"
|
|
41
|
+
KEY_FILE="$TARGET_DIR/${NAME_SAFE}-key.pem"
|
|
42
|
+
FULLCHAIN_FILE="$TARGET_DIR/${NAME_SAFE}-fullchain.pem"
|
|
43
|
+
ROOT_PEM="$TARGET_DIR/rootCA.pem"
|
|
44
|
+
|
|
45
|
+
info "Target dir: $TARGET_DIR"
|
|
46
|
+
info "Domains: ${DOMAIN_ARR[*]}"
|
|
47
|
+
info "Outputs: $CERT_FILE, $KEY_FILE, $FULLCHAIN_FILE, $ROOT_PEM"
|
|
48
|
+
|
|
49
|
+
# Install prerequisites
|
|
50
|
+
if ! command -v dnf >/dev/null 2>&1; then err "dnf not found. This script expects RHEL/Rocky with dnf."; exit 1; fi
|
|
51
|
+
sudo dnf install -y curl nss-tools ca-certificates || true
|
|
52
|
+
|
|
53
|
+
# Download and install mkcert binary (no 'go install')
|
|
54
|
+
download_mkcert_binary() {
|
|
55
|
+
UNAME_M=$(uname -m)
|
|
56
|
+
case "$UNAME_M" in
|
|
57
|
+
x86_64|amd64) ARCH_STR='linux-amd64' ;;
|
|
58
|
+
aarch64|arm64) ARCH_STR='linux-arm64' ;;
|
|
59
|
+
*) ARCH_STR='linux-amd64' ;;
|
|
60
|
+
esac
|
|
61
|
+
info "Searching mkcert release for $ARCH_STR"
|
|
62
|
+
ASSET_URL=$(curl -sS "https://api.github.com/repos/FiloSottile/mkcert/releases/latest" | \
|
|
63
|
+
grep -E '"browser_download_url":' | grep -i "$ARCH_STR" | head -n1 | sed -E 's/.*"(https:[^"]+)".*/\1/' || true)
|
|
64
|
+
if [[ -z "$ASSET_URL" ]]; then
|
|
65
|
+
ASSET_URL=$(curl -sS "https://api.github.com/repos/FiloSottile/mkcert/releases/latest" | \
|
|
66
|
+
grep -E '"browser_download_url":' | grep -i 'linux' | grep -i 'amd64' | head -n1 | sed -E 's/.*"(https:[^"]+)".*/\1/' || true)
|
|
67
|
+
fi
|
|
68
|
+
if [[ -z "$ASSET_URL" ]]; then err "Could not find mkcert asset for your arch"; return 1; fi
|
|
69
|
+
TMP_BIN="$(mktemp -u /tmp/mkcert.XXXXXX)"
|
|
70
|
+
if curl -fSL -o "$TMP_BIN" "$ASSET_URL"; then
|
|
71
|
+
sudo mv "$TMP_BIN" /usr/local/bin/mkcert
|
|
72
|
+
sudo chmod +x /usr/local/bin/mkcert
|
|
73
|
+
info "mkcert installed to /usr/local/bin/mkcert"
|
|
74
|
+
return 0
|
|
75
|
+
fi
|
|
76
|
+
return 1
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
use_mkcert() {
|
|
80
|
+
if ! command -v mkcert >/dev/null 2>&1; then
|
|
81
|
+
info "mkcert not found — attempting to download/install binary"
|
|
82
|
+
download_mkcert_binary || return 1
|
|
83
|
+
fi
|
|
84
|
+
MKCERT_BIN="$(command -v mkcert || echo /usr/local/bin/mkcert)"
|
|
85
|
+
info "Running mkcert -install as sudo (if necessary)"
|
|
86
|
+
if ! sudo "$MKCERT_BIN" -install >/dev/null 2>&1; then
|
|
87
|
+
if ! "$MKCERT_BIN" -install >/dev/null 2>&1; then
|
|
88
|
+
err "mkcert -install failed"; return 1
|
|
89
|
+
fi
|
|
90
|
+
fi
|
|
91
|
+
MK_ARGS=()
|
|
92
|
+
for d in "${DOMAIN_ARR[@]}"; do MK_ARGS+=("$d"); done
|
|
93
|
+
info "Generating cert+key with mkcert"
|
|
94
|
+
if ! "$MKCERT_BIN" -cert-file "$CERT_FILE" -key-file "$KEY_FILE" "${MK_ARGS[@]}"; then err "mkcert failed to generate"; return 1; fi
|
|
95
|
+
# copy root CA from mkcert CAROOT into TARGET_DIR
|
|
96
|
+
if ROOT_FROM_MKCERT="$($MKCERT_BIN -CAROOT 2>/dev/null || true)"; then
|
|
97
|
+
if [[ -f "$ROOT_FROM_MKCERT/rootCA.pem" ]]; then
|
|
98
|
+
cp "$ROOT_FROM_MKCERT/rootCA.pem" "$ROOT_PEM"
|
|
99
|
+
info "Copied mkcert root CA to $ROOT_PEM"
|
|
100
|
+
fi
|
|
101
|
+
fi
|
|
102
|
+
# create fullchain (cert followed by root CA)
|
|
103
|
+
if [[ -f "$ROOT_PEM" ]]; then
|
|
104
|
+
cat "$CERT_FILE" "$ROOT_PEM" > "$FULLCHAIN_FILE"
|
|
105
|
+
info "Created fullchain: $FULLCHAIN_FILE"
|
|
106
|
+
else
|
|
107
|
+
cp "$CERT_FILE" "$FULLCHAIN_FILE"
|
|
108
|
+
info "No root CA found to append; fullchain contains only leaf cert: $FULLCHAIN_FILE"
|
|
109
|
+
fi
|
|
110
|
+
return 0
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
use_openssl() {
|
|
114
|
+
command -v openssl >/dev/null 2>&1 || { err "openssl required"; return 1; }
|
|
115
|
+
ROOT_KEY="$TARGET_DIR/rootCA.key"
|
|
116
|
+
if [[ ! -f "$ROOT_KEY" || ! -f "$ROOT_PEM" ]]; then
|
|
117
|
+
openssl genrsa -out "$ROOT_KEY" 4096
|
|
118
|
+
openssl req -x509 -new -nodes -key "$ROOT_KEY" -sha256 -days 3650 -out "$ROOT_PEM" -subj "/CN=Local Development Root CA"
|
|
119
|
+
fi
|
|
120
|
+
CSR_KEY="$TARGET_DIR/${NAME_SAFE}.key"
|
|
121
|
+
CSR_PEM="$TARGET_DIR/${NAME_SAFE}.csr"
|
|
122
|
+
openssl genrsa -out "$CSR_KEY" 2048
|
|
123
|
+
openssl req -new -key "$CSR_KEY" -out "$CSR_PEM" -subj "/CN=${DOMAIN_ARR[0]}"
|
|
124
|
+
V3EXT="$TARGET_DIR/v3.ext"
|
|
125
|
+
printf 'authorityKeyIdentifier=keyid,issuer\nbasicConstraints=CA:FALSE\nkeyUsage = digitalSignature, keyEncipherment\nsubjectAltName = @alt_names\n\n[alt_names]\n' > "$V3EXT"
|
|
126
|
+
DNS=1; IP=1
|
|
127
|
+
for d in "${DOMAIN_ARR[@]}"; do
|
|
128
|
+
if [[ "$d" =~ ^[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+$ ]] || [[ "$d" == *":"* ]]; then
|
|
129
|
+
printf 'IP.%d = %s\n' "$IP" "$d" >> "$V3EXT"; IP=$((IP+1))
|
|
130
|
+
else
|
|
131
|
+
printf 'DNS.%d = %s\n' "$DNS" "$d" >> "$V3EXT"; DNS=$((DNS+1))
|
|
132
|
+
fi
|
|
133
|
+
done
|
|
134
|
+
if openssl x509 -req -in "$CSR_PEM" -CA "$ROOT_PEM" -CAkey "$ROOT_KEY" -CAcreateserial -out "$CERT_FILE" -days 825 -sha256 -extfile "$V3EXT"; then
|
|
135
|
+
mv -f "$CSR_KEY" "$KEY_FILE"
|
|
136
|
+
# create fullchain: leaf + root
|
|
137
|
+
cat "$CERT_FILE" "$ROOT_PEM" > "$FULLCHAIN_FILE"
|
|
138
|
+
sudo cp "$ROOT_PEM" /etc/pki/ca-trust/source/anchors/ || true
|
|
139
|
+
sudo update-ca-trust extract || true
|
|
140
|
+
if command -v certutil >/dev/null 2>&1; then
|
|
141
|
+
mkdir -p "$HOME/.pki/nssdb"
|
|
142
|
+
certutil -d sql:$HOME/.pki/nssdb -A -t "CT,C,C" -n "Local Dev Root CA" -i "$ROOT_PEM" || true
|
|
143
|
+
fi
|
|
144
|
+
info "OpenSSL created cert, key and fullchain"
|
|
145
|
+
return 0
|
|
146
|
+
fi
|
|
147
|
+
err "OpenSSL failed to create certificate"
|
|
148
|
+
return 1
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
# main
|
|
152
|
+
if use_mkcert; then
|
|
153
|
+
info "Done: created cert, key, fullchain and root CA in $TARGET_DIR"
|
|
154
|
+
exit 0
|
|
155
|
+
else
|
|
156
|
+
info "mkcert flow failed — trying OpenSSL fallback"
|
|
157
|
+
if use_openssl; then
|
|
158
|
+
info "Done: created cert, key, fullchain and root CA in $TARGET_DIR"
|
|
159
|
+
exit 0
|
|
160
|
+
fi
|
|
161
|
+
fi
|
|
162
|
+
|
|
163
|
+
err "Failed to create certificates with mkcert or OpenSSL"
|
|
164
|
+
exit 2
|
package/src/cli/baremetal.js
CHANGED
|
@@ -11,7 +11,7 @@ import dotenv from 'dotenv';
|
|
|
11
11
|
import { loggerFactory } from '../server/logger.js';
|
|
12
12
|
import { getLocalIPv4Address } from '../server/dns.js';
|
|
13
13
|
import fs from 'fs-extra';
|
|
14
|
-
import
|
|
14
|
+
import Downloader from '../server/downloader.js';
|
|
15
15
|
import UnderpostCloudInit from './cloud-init.js';
|
|
16
16
|
import { s4, timer } from '../client/components/core/CommonJs.js';
|
|
17
17
|
|
|
@@ -153,10 +153,10 @@ class UnderpostBaremetal {
|
|
|
153
153
|
// Handle control server installation.
|
|
154
154
|
if (options.controlServerInstall === true) {
|
|
155
155
|
// Ensure scripts are executable and then run them.
|
|
156
|
-
shellExec(`chmod +x ${underpostRoot}/
|
|
157
|
-
shellExec(`chmod +x ${underpostRoot}/
|
|
158
|
-
shellExec(`${underpostRoot}/
|
|
159
|
-
shellExec(`${underpostRoot}/
|
|
156
|
+
shellExec(`chmod +x ${underpostRoot}/scripts/maas-setup.sh`);
|
|
157
|
+
shellExec(`chmod +x ${underpostRoot}/scripts/nat-iptables.sh`);
|
|
158
|
+
shellExec(`${underpostRoot}/scripts/maas-setup.sh`);
|
|
159
|
+
shellExec(`${underpostRoot}/scripts/nat-iptables.sh`);
|
|
160
160
|
return;
|
|
161
161
|
}
|
|
162
162
|
|
|
@@ -349,7 +349,7 @@ class UnderpostBaremetal {
|
|
|
349
349
|
const name = url.split('/').pop().replace('.zip', '');
|
|
350
350
|
const path = `../${name}`;
|
|
351
351
|
if (!fs.existsSync(path)) {
|
|
352
|
-
await Downloader(url, `../${name}.zip`); // Download firmware if not exists.
|
|
352
|
+
await Downloader.downloadFile(url, `../${name}.zip`); // Download firmware if not exists.
|
|
353
353
|
shellExec(`cd .. && mkdir ${name} && cd ${name} && unzip ../${name}.zip`); // Unzip firmware.
|
|
354
354
|
}
|
|
355
355
|
shellExec(`sudo cp -a ${path}/* ${tftpRootPath}`); // Copy firmware files to TFTP root.
|
|
@@ -530,7 +530,7 @@ menuentry '${menuentryStr}' {
|
|
|
530
530
|
if (options.cloudInitUpdate === true) return;
|
|
531
531
|
|
|
532
532
|
// Apply NAT iptables rules.
|
|
533
|
-
shellExec(`${underpostRoot}/
|
|
533
|
+
shellExec(`${underpostRoot}/scripts/nat-iptables.sh`, { silent: true });
|
|
534
534
|
|
|
535
535
|
// Wait for MAC address assignment.
|
|
536
536
|
logger.info('Waiting for MAC assignment...');
|
|
@@ -952,7 +952,7 @@ EOF`);
|
|
|
952
952
|
*/
|
|
953
953
|
getHostArch() {
|
|
954
954
|
// `uname -m` returns e.g. 'x86_64' or 'aarch64'
|
|
955
|
-
const machine = shellExec('uname -m', { stdout: true }).trim();
|
|
955
|
+
const machine = shellExec('uname -m', { stdout: true, silent: true }).trim();
|
|
956
956
|
if (machine === 'x86_64') return { alias: 'amd64', name: 'x86_64' };
|
|
957
957
|
if (machine === 'aarch64') return { alias: 'arm64', name: 'aarch64' };
|
|
958
958
|
throw new Error(`Unsupported host architecture: ${machine}`);
|
package/src/cli/cloud-init.js
CHANGED
|
@@ -182,7 +182,7 @@ cut -d: -f1 /etc/passwd`,
|
|
|
182
182
|
|
|
183
183
|
// Copy the device scan script from manifests.
|
|
184
184
|
logger.info('Build', `${nfsHostToolsPath}/device_scan.sh`);
|
|
185
|
-
fs.copySync(`${underpostRoot}/
|
|
185
|
+
fs.copySync(`${underpostRoot}/scripts/device-scan.sh`, `${nfsHostToolsPath}/device_scan.sh`);
|
|
186
186
|
|
|
187
187
|
// Build and write the config path script.
|
|
188
188
|
logger.info('Build', `${nfsHostToolsPath}/config-path.sh`);
|
package/src/cli/cluster.js
CHANGED
|
@@ -15,6 +15,13 @@ import fs from 'fs-extra';
|
|
|
15
15
|
|
|
16
16
|
const logger = loggerFactory(import.meta);
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @class UnderpostCluster
|
|
20
|
+
* @description Manages Kubernetes cluster initialization, configuration, and component deployment.
|
|
21
|
+
* This class provides a set of static methods to handle cluster initialization, configuration,
|
|
22
|
+
* and optional component deployments.
|
|
23
|
+
* @memberof UnderpostCluster
|
|
24
|
+
*/
|
|
18
25
|
class UnderpostCluster {
|
|
19
26
|
static API = {
|
|
20
27
|
/**
|
|
@@ -520,7 +527,7 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
|
|
|
520
527
|
|
|
521
528
|
// shellExec(`sudo sysctl --system`); // Apply sysctl changes immediately
|
|
522
529
|
// Apply NAT iptables rules.
|
|
523
|
-
shellExec(`${underpostRoot}/
|
|
530
|
+
shellExec(`${underpostRoot}/scripts/nat-iptables.sh`, { silent: true });
|
|
524
531
|
|
|
525
532
|
// Disable firewalld (common cause of network issues in Kubernetes)
|
|
526
533
|
shellExec(`sudo systemctl stop firewalld || true`); // Stop if running
|
|
@@ -663,8 +670,8 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
|
|
|
663
670
|
shellExec('sudo iptables -F || true');
|
|
664
671
|
shellExec('sudo iptables -t nat -F || true');
|
|
665
672
|
// Restore iptables rules
|
|
666
|
-
shellExec(`chmod +x ${options.underpostRoot}/
|
|
667
|
-
shellExec(`${options.underpostRoot}/
|
|
673
|
+
shellExec(`chmod +x ${options.underpostRoot}/scripts/nat-iptables.sh`);
|
|
674
|
+
shellExec(`${options.underpostRoot}/scripts/nat-iptables.sh`, { silent: true });
|
|
668
675
|
shellExec('sudo ip link del cni0 || true');
|
|
669
676
|
shellExec('sudo ip link del flannel.1 || true');
|
|
670
677
|
|
|
@@ -691,7 +698,11 @@ net.ipv4.ip_forward = 1' | sudo tee ${iptableConfPath}`,
|
|
|
691
698
|
*/
|
|
692
699
|
getResourcesCapacity(node) {
|
|
693
700
|
const resources = {};
|
|
694
|
-
const nodeName = node
|
|
701
|
+
const nodeName = node
|
|
702
|
+
? node
|
|
703
|
+
: UnderpostDeploy.API.get('kind-control-plane', 'node').length > 0
|
|
704
|
+
? 'kind-control-plane'
|
|
705
|
+
: os.hostname();
|
|
695
706
|
const info = shellExec(`kubectl describe node ${nodeName} | grep -E '(Allocatable:|Capacity:)' -A 6`, {
|
|
696
707
|
stdout: true,
|
|
697
708
|
silent: true,
|
package/src/cli/cron.js
CHANGED
package/src/cli/db.js
CHANGED
|
@@ -271,10 +271,11 @@ class UnderpostDB {
|
|
|
271
271
|
const deployId = _deployId.trim();
|
|
272
272
|
if (!deployId) continue;
|
|
273
273
|
const confServer = loadReplicas(
|
|
274
|
+
deployId,
|
|
274
275
|
JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
|
|
275
276
|
);
|
|
276
277
|
const router = await UnderpostDeploy.API.routerFactory(deployId, env);
|
|
277
|
-
const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
|
|
278
|
+
const pathPortAssignmentData = await pathPortAssignmentFactory(deployId, router, confServer);
|
|
278
279
|
|
|
279
280
|
for (const host of Object.keys(confServer)) {
|
|
280
281
|
for (const { path, port } of pathPortAssignmentData[host]) {
|
package/src/cli/deploy.js
CHANGED
|
@@ -20,6 +20,10 @@ import fs from 'fs-extra';
|
|
|
20
20
|
import dotenv from 'dotenv';
|
|
21
21
|
import UnderpostRootEnv from './env.js';
|
|
22
22
|
import UnderpostCluster from './cluster.js';
|
|
23
|
+
import { timer } from '../client/components/core/CommonJs.js';
|
|
24
|
+
import os from 'node:os';
|
|
25
|
+
import Dns, { getLocalIPv4Address } from '../server/dns.js';
|
|
26
|
+
import UnderpostBaremetal from './baremetal.js';
|
|
23
27
|
|
|
24
28
|
const logger = loggerFactory(import.meta);
|
|
25
29
|
|
|
@@ -183,10 +187,11 @@ spec:
|
|
|
183
187
|
const deployId = _deployId.trim();
|
|
184
188
|
if (!deployId) continue;
|
|
185
189
|
const confServer = loadReplicas(
|
|
190
|
+
deployId,
|
|
186
191
|
JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8')),
|
|
187
192
|
);
|
|
188
193
|
const router = await UnderpostDeploy.API.routerFactory(deployId, env);
|
|
189
|
-
const pathPortAssignmentData = pathPortAssignmentFactory(router, confServer);
|
|
194
|
+
const pathPortAssignmentData = await pathPortAssignmentFactory(deployId, router, confServer);
|
|
190
195
|
const { fromPort, toPort } = deployRangePortFactory(router);
|
|
191
196
|
const deploymentVersions = options.versions.split(',');
|
|
192
197
|
fs.mkdirSync(`./engine-private/conf/${deployId}/build/${env}`, { recursive: true });
|
|
@@ -226,12 +231,12 @@ metadata:
|
|
|
226
231
|
spec:
|
|
227
232
|
virtualhost:
|
|
228
233
|
fqdn: ${host}${
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
234
|
+
env === 'development'
|
|
235
|
+
? ''
|
|
236
|
+
: `
|
|
232
237
|
tls:
|
|
233
238
|
secretName: ${host}`
|
|
234
|
-
|
|
239
|
+
}
|
|
235
240
|
routes:`;
|
|
236
241
|
for (const conditionObj of pathPortAssignment) {
|
|
237
242
|
const { path, port } = conditionObj;
|
|
@@ -324,7 +329,8 @@ spec:
|
|
|
324
329
|
* @param {string} options.node - Node name for resource allocation.
|
|
325
330
|
* @param {string} options.restoreHosts - Whether to restore hosts.
|
|
326
331
|
* @param {string} options.disableUpdateDeployment - Whether to disable updating the deployment.
|
|
327
|
-
* @param {string} options.
|
|
332
|
+
* @param {string} options.disableUpdateProxy - Whether to disable updating the proxy.
|
|
333
|
+
* @param {string} options.status - Whether to display status host machine server and traffic information.
|
|
328
334
|
* @param {string} options.etcHosts - Whether to update /etc/hosts.
|
|
329
335
|
* @returns {Promise<void>} - Promise that resolves when the callback is complete.
|
|
330
336
|
* @memberof UnderpostDeploy
|
|
@@ -348,7 +354,8 @@ spec:
|
|
|
348
354
|
node: '',
|
|
349
355
|
restoreHosts: false,
|
|
350
356
|
disableUpdateDeployment: false,
|
|
351
|
-
|
|
357
|
+
disableUpdateProxy: false,
|
|
358
|
+
status: false,
|
|
352
359
|
etcHosts: false,
|
|
353
360
|
},
|
|
354
361
|
) {
|
|
@@ -414,7 +421,7 @@ EOF`);
|
|
|
414
421
|
} else if (!deployList) deployList = 'dd-default';
|
|
415
422
|
if (deployList === 'dd' && fs.existsSync(`./engine-private/deploy/dd.router`))
|
|
416
423
|
deployList = fs.readFileSync(`./engine-private/deploy/dd.router`, 'utf8');
|
|
417
|
-
if (options.
|
|
424
|
+
if (options.status === true) {
|
|
418
425
|
for (const _deployId of deployList.split(',')) {
|
|
419
426
|
const deployId = _deployId.trim();
|
|
420
427
|
logger.info('', {
|
|
@@ -425,6 +432,16 @@ EOF`);
|
|
|
425
432
|
pods: await UnderpostDeploy.API.get(deployId),
|
|
426
433
|
});
|
|
427
434
|
}
|
|
435
|
+
const interfaceName = Dns.getDefaultNetworkInterface();
|
|
436
|
+
logger.info('Machine', {
|
|
437
|
+
node: os.hostname(),
|
|
438
|
+
arch: UnderpostBaremetal.API.getHostArch(),
|
|
439
|
+
ipv4Public: await Dns.getPublicIp(),
|
|
440
|
+
ipv4Local: getLocalIPv4Address(),
|
|
441
|
+
resources: UnderpostCluster.API.getResourcesCapacity(),
|
|
442
|
+
defaultInterfaceName: interfaceName,
|
|
443
|
+
defaultInterfaceInfo: os.networkInterfaces()[interfaceName],
|
|
444
|
+
});
|
|
428
445
|
return;
|
|
429
446
|
}
|
|
430
447
|
if (!(options.versions && typeof options.versions === 'string')) options.versions = 'blue,green';
|
|
@@ -466,11 +483,14 @@ EOF`);
|
|
|
466
483
|
}
|
|
467
484
|
|
|
468
485
|
const confServer = JSON.parse(fs.readFileSync(`./engine-private/conf/${deployId}/conf.server.json`, 'utf8'));
|
|
486
|
+
|
|
469
487
|
for (const host of Object.keys(confServer)) {
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
488
|
+
if (!options.disableUpdateProxy) {
|
|
489
|
+
shellExec(`sudo kubectl delete HTTPProxy ${host}`);
|
|
490
|
+
if (UnderpostDeploy.API.isValidTLSContext({ host, env, options }))
|
|
491
|
+
shellExec(`sudo kubectl delete Certificate ${host}`);
|
|
492
|
+
}
|
|
493
|
+
if (!options.remove) etcHosts.push(host);
|
|
474
494
|
}
|
|
475
495
|
|
|
476
496
|
const manifestsPath =
|
|
@@ -478,9 +498,9 @@ EOF`);
|
|
|
478
498
|
? `engine-private/conf/${deployId}/build/production`
|
|
479
499
|
: `manifests/deployment/${deployId}-${env}`;
|
|
480
500
|
|
|
481
|
-
if (!options.remove
|
|
501
|
+
if (!options.remove) {
|
|
482
502
|
if (!options.disableUpdateDeployment) shellExec(`sudo kubectl apply -f ./${manifestsPath}/deployment.yaml`);
|
|
483
|
-
shellExec(`sudo kubectl apply -f ./${manifestsPath}/proxy.yaml`);
|
|
503
|
+
if (!options.disableUpdateProxy) shellExec(`sudo kubectl apply -f ./${manifestsPath}/proxy.yaml`);
|
|
484
504
|
|
|
485
505
|
if (UnderpostDeploy.API.isValidTLSContext({ host: Object.keys(confServer)[0], env, options }))
|
|
486
506
|
shellExec(`sudo kubectl apply -f ./${manifestsPath}/secret.yaml`);
|
|
@@ -655,6 +675,37 @@ EOF`);
|
|
|
655
675
|
env === 'production' &&
|
|
656
676
|
options.cert === true &&
|
|
657
677
|
(!options.certHosts || options.certHosts.split(',').includes(host)),
|
|
678
|
+
|
|
679
|
+
/**
|
|
680
|
+
* Monitors the ready status of a deployment.
|
|
681
|
+
* @param {string} deployId - Deployment ID for which the ready status is being monitored.
|
|
682
|
+
* @param {string} env - Environment for which the ready status is being monitored.
|
|
683
|
+
* @param {string} targetTraffic - Target traffic status for the deployment.
|
|
684
|
+
* @param {Array<string>} ignorePods - List of pod names to ignore.
|
|
685
|
+
* @memberof UnderpostDeploy
|
|
686
|
+
*/
|
|
687
|
+
async monitorReadyRunner(deployId, env, targetTraffic, ignorePods = []) {
|
|
688
|
+
let checkStatusIteration = 0;
|
|
689
|
+
const checkStatusIterationMsDelay = 1000;
|
|
690
|
+
const iteratorTag = `[${deployId}-${env}-${targetTraffic}]`;
|
|
691
|
+
logger.info('Deployment init', { deployId, env, targetTraffic, checkStatusIterationMsDelay });
|
|
692
|
+
const minReadyOk = 3;
|
|
693
|
+
let readyOk = 0;
|
|
694
|
+
|
|
695
|
+
while (readyOk < minReadyOk) {
|
|
696
|
+
const ready = UnderpostDeploy.API.checkDeploymentReadyStatus(deployId, env, targetTraffic, ignorePods).ready;
|
|
697
|
+
if (ready === true) {
|
|
698
|
+
readyOk++;
|
|
699
|
+
logger.info(`${iteratorTag} | Deployment ready. Verification number: ${readyOk}`);
|
|
700
|
+
}
|
|
701
|
+
await timer(checkStatusIterationMsDelay);
|
|
702
|
+
checkStatusIteration++;
|
|
703
|
+
logger.info(
|
|
704
|
+
`${iteratorTag} | Deployment in progress... | Delay number check iterations: ${checkStatusIteration}`,
|
|
705
|
+
);
|
|
706
|
+
}
|
|
707
|
+
logger.info(`${iteratorTag} | Deployment ready. | Total delay number check iterations: ${checkStatusIteration}`);
|
|
708
|
+
},
|
|
658
709
|
};
|
|
659
710
|
}
|
|
660
711
|
|
package/src/cli/fs.js
CHANGED
|
@@ -10,7 +10,7 @@ import dotenv from 'dotenv';
|
|
|
10
10
|
import AdmZip from 'adm-zip';
|
|
11
11
|
import * as dir from 'path';
|
|
12
12
|
import fs from 'fs-extra';
|
|
13
|
-
import
|
|
13
|
+
import Downloader from '../server/downloader.js';
|
|
14
14
|
import UnderpostRepository from './repository.js';
|
|
15
15
|
import { shellExec } from '../server/process.js';
|
|
16
16
|
dotenv.config();
|
|
@@ -204,7 +204,7 @@ class UnderpostFileStorage {
|
|
|
204
204
|
resource_type: 'raw',
|
|
205
205
|
});
|
|
206
206
|
logger.info('download result', downloadResult);
|
|
207
|
-
await Downloader(downloadResult, path + '.zip');
|
|
207
|
+
await Downloader.downloadFile(downloadResult, path + '.zip');
|
|
208
208
|
path = UnderpostFileStorage.API.zip2File(path + '.zip');
|
|
209
209
|
fs.removeSync(path + '.zip');
|
|
210
210
|
},
|
package/src/cli/image.js
CHANGED
|
@@ -15,6 +15,13 @@ dotenv.config();
|
|
|
15
15
|
|
|
16
16
|
const logger = loggerFactory(import.meta);
|
|
17
17
|
|
|
18
|
+
/**
|
|
19
|
+
* @class UnderpostImage
|
|
20
|
+
* @description Manages Docker image operations, including pulling, building, and loading images into Kubernetes clusters.
|
|
21
|
+
* This class provides a set of static methods to handle image operations, including pulling base images,
|
|
22
|
+
* building custom images, and loading them into specified Kubernetes clusters (Kind, Kubeadm, or K3s).
|
|
23
|
+
* @memberof UnderpostImage
|
|
24
|
+
*/
|
|
18
25
|
class UnderpostImage {
|
|
19
26
|
static API = {
|
|
20
27
|
dockerfile: {
|
|
@@ -27,6 +34,7 @@ class UnderpostImage {
|
|
|
27
34
|
* @param {boolean} [options.kubeadmLoad=false] - If true, load image into Kubeadm cluster.
|
|
28
35
|
* @param {boolean} [options.k3sLoad=false] - If true, load image into K3s cluster.
|
|
29
36
|
* @param {string} [options.path=false] - Path to the Dockerfile context.
|
|
37
|
+
* @param {boolean} [options.dev=false] - If true, use development mode.
|
|
30
38
|
* @param {string} [options.version=''] - Version tag for the image.
|
|
31
39
|
* @memberof UnderpostImage
|
|
32
40
|
*/
|
|
@@ -36,11 +44,14 @@ class UnderpostImage {
|
|
|
36
44
|
kubeadmLoad: false,
|
|
37
45
|
k3sLoad: false,
|
|
38
46
|
path: false,
|
|
47
|
+
dev: false,
|
|
39
48
|
version: '',
|
|
40
49
|
},
|
|
41
50
|
) {
|
|
42
51
|
// shellExec(`sudo podman pull docker.io/library/debian:buster`);
|
|
43
52
|
shellExec(`sudo podman pull docker.io/library/rockylinux:9`);
|
|
53
|
+
const baseCommand = options.dev ? 'node bin' : 'underpost';
|
|
54
|
+
const baseCommandOption = options.dev ? ' --dev' : '';
|
|
44
55
|
const IMAGE_NAME = `rockylinux9-underpost`;
|
|
45
56
|
const IMAGE_NAME_FULL = `${IMAGE_NAME}:${options.version ?? Underpost.version}`;
|
|
46
57
|
let LOAD_TYPE = '';
|
|
@@ -54,7 +65,7 @@ class UnderpostImage {
|
|
|
54
65
|
}
|
|
55
66
|
|
|
56
67
|
shellExec(
|
|
57
|
-
|
|
68
|
+
`${baseCommand} dockerfile-image-build${baseCommandOption} --podman-save --reset --image-path=. --path ${
|
|
58
69
|
options.path ?? getUnderpostRootPath()
|
|
59
70
|
} --image-name=${IMAGE_NAME_FULL} ${LOAD_TYPE}`,
|
|
60
71
|
);
|
|
@@ -75,6 +86,7 @@ class UnderpostImage {
|
|
|
75
86
|
* @param {boolean} [options.secrets=false] - If true, load secrets from the .env file for the build.
|
|
76
87
|
* @param {string} [options.secretsPath=''] - Custom path to the .env file for secrets.
|
|
77
88
|
* @param {boolean} [options.reset=false] - If true, perform a no-cache build.
|
|
89
|
+
* @param {boolean} [options.dev=false] - If true, use development mode.
|
|
78
90
|
* @memberof UnderpostImage
|
|
79
91
|
*/
|
|
80
92
|
build(
|
|
@@ -90,6 +102,7 @@ class UnderpostImage {
|
|
|
90
102
|
secrets: false,
|
|
91
103
|
secretsPath: '',
|
|
92
104
|
reset: false,
|
|
105
|
+
dev: false,
|
|
93
106
|
},
|
|
94
107
|
) {
|
|
95
108
|
const {
|
|
@@ -104,6 +117,7 @@ class UnderpostImage {
|
|
|
104
117
|
kubeadmLoad,
|
|
105
118
|
k3sLoad,
|
|
106
119
|
reset,
|
|
120
|
+
dev,
|
|
107
121
|
} = options;
|
|
108
122
|
const podManImg = `localhost/${imageName}`;
|
|
109
123
|
if (imagePath && typeof imagePath === 'string' && !fs.existsSync(imagePath))
|
|
@@ -132,7 +146,10 @@ class UnderpostImage {
|
|
|
132
146
|
} -t ${imageName} --pull=never --cap-add=CAP_AUDIT_WRITE${cache}${secretDockerInput} --network host`,
|
|
133
147
|
);
|
|
134
148
|
|
|
135
|
-
if (podmanSave === true)
|
|
149
|
+
if (podmanSave === true) {
|
|
150
|
+
if (fs.existsSync(tarFile)) fs.removeSync(tarFile);
|
|
151
|
+
shellExec(`podman save -o ${tarFile} ${podManImg}`);
|
|
152
|
+
}
|
|
136
153
|
if (kindLoad === true) shellExec(`sudo kind load image-archive ${tarFile}`);
|
|
137
154
|
if (kubeadmLoad === true) {
|
|
138
155
|
// Use 'ctr' for Kubeadm
|