@underpostnet/underpost 2.96.0 → 2.97.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/.dockerignore +1 -2
- package/.env.development +0 -3
- package/.env.production +0 -3
- package/.env.test +0 -3
- package/.prettierignore +1 -2
- package/README.md +31 -31
- package/baremetal/commission-workflows.json +64 -17
- package/baremetal/packer-workflows.json +11 -0
- package/cli.md +72 -40
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +4 -4
- package/package.json +3 -2
- package/packer/images/Rocky9Amd64/rocky9.pkr.hcl +6 -2
- package/packer/images/Rocky9Arm64/Makefile +69 -0
- package/packer/images/Rocky9Arm64/README.md +122 -0
- package/packer/images/Rocky9Arm64/http/rocky9.ks.pkrtpl.hcl +114 -0
- package/packer/images/Rocky9Arm64/rocky9.pkr.hcl +171 -0
- package/scripts/disk-clean.sh +128 -187
- package/scripts/ipxe-setup.sh +197 -0
- package/scripts/packer-init-vars-file.sh +16 -6
- package/scripts/packer-setup.sh +270 -33
- package/scripts/ports-ls.sh +31 -0
- package/scripts/quick-tftp.sh +19 -0
- package/src/api/document/document.controller.js +15 -0
- package/src/api/document/document.model.js +14 -0
- package/src/api/document/document.router.js +1 -0
- package/src/api/document/document.service.js +61 -3
- package/src/cli/baremetal.js +1716 -439
- package/src/cli/cloud-init.js +354 -231
- package/src/cli/cluster.js +1 -1
- package/src/cli/db.js +22 -0
- package/src/cli/deploy.js +6 -2
- package/src/cli/image.js +1 -0
- package/src/cli/index.js +40 -36
- package/src/cli/run.js +77 -11
- package/src/cli/ssh.js +1 -1
- package/src/client/components/core/Input.js +3 -1
- package/src/client/components/core/Panel.js +161 -15
- package/src/client/components/core/PanelForm.js +198 -35
- package/src/client/components/core/Translate.js +11 -0
- package/src/client/services/document/document.service.js +19 -0
- package/src/index.js +2 -1
- package/src/server/dns.js +8 -2
- package/src/server/start.js +14 -6
- package/manifests/mariadb/config.yaml +0 -10
- package/manifests/mariadb/secret.yaml +0 -8
package/scripts/packer-setup.sh
CHANGED
|
@@ -1,52 +1,289 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
3
|
-
#
|
|
2
|
+
# packer-setup.sh
|
|
3
|
+
# RHEL/CentOS/Rocky helper to prepare a host for building both amd64 and aarch64 Packer images.
|
|
4
|
+
# Installs packer (repo fallback), libvirt/qemu tooling, NBD/filesystem tools, UEFI firmware for x86_64 and aarch64,
|
|
5
|
+
# registers qemu-user binfmt for cross-chroot use and compiles qemu-system-aarch64 when repo packages are missing.
|
|
6
|
+
# Usage: sudo ./packer-setup.sh
|
|
4
7
|
|
|
5
8
|
set -euo pipefail
|
|
6
9
|
|
|
10
|
+
print(){ printf "[setup] %s\n" "$*"; }
|
|
11
|
+
err(){ printf "[setup] ERROR: %s\n" "$*" >&2; }
|
|
7
12
|
|
|
8
|
-
|
|
9
|
-
|
|
13
|
+
if [[ $(id -u) -ne 0 ]]; then
|
|
14
|
+
err "This script requires root. Run with sudo."; exit 3
|
|
15
|
+
fi
|
|
16
|
+
|
|
17
|
+
# Detect host arch
|
|
18
|
+
HOST_RAW_ARCH=$(uname -m)
|
|
19
|
+
case "$HOST_RAW_ARCH" in
|
|
20
|
+
x86_64) HOST_ARCH=amd64;;
|
|
21
|
+
aarch64) HOST_ARCH=arm64;;
|
|
22
|
+
*) HOST_ARCH="$HOST_RAW_ARCH";;
|
|
23
|
+
esac
|
|
24
|
+
print "Host architecture: $HOST_RAW_ARCH -> normalized: $HOST_ARCH"
|
|
25
|
+
|
|
26
|
+
# Ensure RHEL-family
|
|
27
|
+
if [[ -f /etc/os-release ]]; then
|
|
28
|
+
. /etc/os-release
|
|
29
|
+
ID_LC=${ID,,}
|
|
30
|
+
ID_LIKE=${ID_LIKE,,}
|
|
31
|
+
else
|
|
32
|
+
ID_LC=unknown; ID_LIKE=unknown
|
|
33
|
+
fi
|
|
34
|
+
if [[ $ID_LC != "rocky" && $ID_LC != "rhel" && $ID_LC != "centos" && $ID_LIKE != *"rhel"* ]]; then
|
|
35
|
+
err "This script targets RHEL/CentOS/Rocky family. Detected: $ID_LC (like: $ID_LIKE). Exiting."; exit 4
|
|
36
|
+
fi
|
|
37
|
+
print "Distro detected: $PRETTY_NAME"
|
|
38
|
+
|
|
39
|
+
# Enable helpful repos and install helpers
|
|
40
|
+
print "Installing dnf-plugins-core and enabling CRB/PowerTools (if available)"
|
|
41
|
+
set +e
|
|
42
|
+
dnf install -y dnf-plugins-core >/dev/null 2>&1 || true
|
|
43
|
+
dnf config-manager --set-enabled crb >/dev/null 2>&1 || true
|
|
44
|
+
dnf config-manager --set-enabled powertools >/dev/null 2>&1 || true
|
|
45
|
+
# EPEL
|
|
46
|
+
if ! rpm -q epel-release >/dev/null 2>&1; then
|
|
47
|
+
print "Installing epel-release"
|
|
48
|
+
dnf install -y epel-release || true
|
|
49
|
+
fi
|
|
50
|
+
set -e
|
|
51
|
+
|
|
52
|
+
# 1) Try to install packer from distro repos, otherwise add HashiCorp repo and install
|
|
53
|
+
print "Attempting to install packer from distro repos"
|
|
54
|
+
if dnf install -y packer >/dev/null 2>&1; then
|
|
55
|
+
print "Packer installed from distro repo"
|
|
56
|
+
else
|
|
57
|
+
print "packer not available in distro repos or install failed. Adding HashiCorp repo and retrying."
|
|
58
|
+
# HashiCorp RPM repo for RHEL/CentOS/Rocky family (works for EL8/EL9)
|
|
59
|
+
if ! rpm -q hashicorp >/dev/null 2>&1; then
|
|
60
|
+
cat >/etc/yum.repos.d/hashicorp.repo <<'EOF'
|
|
61
|
+
[hashicorp]
|
|
62
|
+
name=HashiCorp Stable - $basearch
|
|
63
|
+
baseurl=https://rpm.releases.hashicorp.com/RHEL/$releasever/$basearch/stable
|
|
64
|
+
enabled=1
|
|
65
|
+
gpgcheck=1
|
|
66
|
+
gpgkey=https://rpm.releases.hashicorp.com/gpg
|
|
67
|
+
EOF
|
|
68
|
+
fi
|
|
69
|
+
if dnf makecache >/dev/null 2>&1 && dnf install -y packer >/dev/null 2>&1; then
|
|
70
|
+
print "Packer installed from HashiCorp repo"
|
|
71
|
+
else
|
|
72
|
+
err "Packer install from repo failed. You can install packer manually from HashiCorp releases if needed.";
|
|
73
|
+
fi
|
|
74
|
+
fi
|
|
10
75
|
|
|
11
|
-
#
|
|
12
|
-
|
|
13
|
-
|
|
76
|
+
# 2) Install libvirt, qemu tooling and enable libvirtd
|
|
77
|
+
print "Installing libvirt, qemu and related tooling (best-effort)"
|
|
78
|
+
LIBVIRT_PKGS=(libvirt libvirt-daemon qemu-kvm qemu-img virt-install bridge-utils)
|
|
79
|
+
# attempt to include qemu-system-aarch64 and qemu-system-x86_64 if available
|
|
80
|
+
LIBVIRT_PKGS+=(qemu-system-aarch64 qemu-system-x86_64 qemu-system-arm)
|
|
14
81
|
|
|
15
|
-
#
|
|
16
|
-
|
|
82
|
+
# Some packages may not exist exactly with those names; install best-effort
|
|
83
|
+
for pkg in "${LIBVIRT_PKGS[@]}"; do
|
|
84
|
+
dnf install -y "$pkg" >/dev/null 2>&1 || print "Package $pkg not available via dnf (skipping)"
|
|
85
|
+
done
|
|
17
86
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
sudo systemctl status libvirtd --no-pager
|
|
87
|
+
print "Enabling and starting libvirtd"
|
|
88
|
+
systemctl enable --now libvirtd || err "Failed to enable/start libvirtd"
|
|
89
|
+
systemctl status libvirtd --no-pager || true
|
|
22
90
|
|
|
23
|
-
#
|
|
24
|
-
|
|
91
|
+
# 3) Install NBD and filesystem tools required for MAAS image creation
|
|
92
|
+
print "Installing NBD and filesystem tooling: libnbd, nbdkit, e2fsprogs, kmod packages (best-effort)"
|
|
93
|
+
NBDS=(libnbd nbdkit e2fsprogs kmod-kvdo kmod)
|
|
94
|
+
for pkg in "${NBDS[@]}"; do
|
|
95
|
+
dnf install -y "$pkg" >/dev/null 2>&1 || print "Package $pkg not available via dnf (skipping)"
|
|
96
|
+
done
|
|
25
97
|
|
|
26
98
|
# 4) Install UEFI firmware for x86_64 and ARM64
|
|
27
|
-
|
|
99
|
+
print "Installing edk2 / OVMF UEFI firmware packages (x86_64 and aarch64)"
|
|
100
|
+
UEFI_PKGS=(edk2-ovmf edk2-aarch64 edk2-ovmf-aarch64)
|
|
101
|
+
for pkg in "${UEFI_PKGS[@]}"; do
|
|
102
|
+
dnf install -y "$pkg" >/dev/null 2>&1 || print "UEFI package $pkg not available (skipping)"
|
|
103
|
+
done
|
|
28
104
|
|
|
29
|
-
# 5)
|
|
30
|
-
|
|
31
|
-
if
|
|
32
|
-
|
|
33
|
-
sudo ln -sf /usr/libexec/qemu-kvm /usr/bin/qemu-system-x86_64
|
|
105
|
+
# 5) Ensure qemu-user-static for chroot/debootstrap cross-execution and register binfmt via podman
|
|
106
|
+
print "Installing qemu-user-static and registering binfmt handlers via podman"
|
|
107
|
+
if ! rpm -q qemu-user-static >/dev/null 2>&1; then
|
|
108
|
+
dnf install -y qemu-user-static >/dev/null 2>&1 || print "qemu-user-static not in repos (will extract from container)"
|
|
34
109
|
fi
|
|
35
110
|
|
|
36
|
-
if
|
|
37
|
-
|
|
38
|
-
|
|
111
|
+
if command -v podman >/dev/null 2>&1; then
|
|
112
|
+
# Register binfmt handlers
|
|
113
|
+
podman run --rm --privileged multiarch/qemu-user-static --reset -p yes || print "podman run for qemu-user-static failed (skip)"
|
|
114
|
+
|
|
115
|
+
# Extract static binaries to /usr/bin if not already present from RPM
|
|
116
|
+
if ! command -v qemu-aarch64-static >/dev/null 2>&1; then
|
|
117
|
+
print "Extracting qemu static binaries from multiarch/qemu-user-static container"
|
|
118
|
+
CONTAINER_ID=$(podman create multiarch/qemu-user-static:latest)
|
|
119
|
+
|
|
120
|
+
# Extract the binaries we need
|
|
121
|
+
for arch in aarch64 arm armeb; do
|
|
122
|
+
if podman cp "$CONTAINER_ID:/usr/bin/qemu-${arch}-static" "/usr/bin/qemu-${arch}-static" 2>/dev/null; then
|
|
123
|
+
chmod +x "/usr/bin/qemu-${arch}-static"
|
|
124
|
+
print "Installed qemu-${arch}-static to /usr/bin/"
|
|
125
|
+
fi
|
|
126
|
+
done
|
|
127
|
+
|
|
128
|
+
podman rm "$CONTAINER_ID" >/dev/null 2>&1 || true
|
|
129
|
+
fi
|
|
130
|
+
else
|
|
131
|
+
print "podman not available. Install podman to register binfmt for container/chroot convenience."
|
|
39
132
|
fi
|
|
40
133
|
|
|
41
|
-
# 6)
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
134
|
+
# 6) Check qemu-system-aarch64 availability and 'virt' machine support; offer compile if missing
|
|
135
|
+
check_qemu_system_aarch64(){
|
|
136
|
+
# Explicitly check /usr/local/bin first (where compiled QEMU installs)
|
|
137
|
+
if [ -x /usr/local/bin/qemu-system-aarch64 ]; then
|
|
138
|
+
QBIN=/usr/local/bin/qemu-system-aarch64
|
|
139
|
+
elif command -v qemu-system-aarch64 >/dev/null 2>&1; then
|
|
140
|
+
QBIN=$(command -v qemu-system-aarch64)
|
|
141
|
+
else
|
|
142
|
+
return 1
|
|
143
|
+
fi
|
|
144
|
+
|
|
145
|
+
print "qemu-system-aarch64 found at $QBIN"
|
|
146
|
+
if ! $QBIN -machine help 2>/dev/null | grep -q '\bvirt\b'; then
|
|
147
|
+
err "qemu-system-aarch64 present but 'virt' not listed -> may be missing aarch64 softmmu"
|
|
148
|
+
return 2
|
|
149
|
+
fi
|
|
150
|
+
|
|
151
|
+
# Check for user networking (slirp) support.
|
|
152
|
+
# We specify -machine virt to avoid "No machine specified" errors on some QEMU versions.
|
|
153
|
+
if ! $QBIN -machine virt -netdev help 2>&1 | grep -q '\buser\b'; then
|
|
154
|
+
err "qemu-system-aarch64 present but 'user' network backend not listed -> missing libslirp support"
|
|
155
|
+
return 3
|
|
156
|
+
fi
|
|
157
|
+
|
|
158
|
+
print "qemu-system-aarch64 supports 'virt' and 'user' network -> good for full-system ARM emulation"
|
|
159
|
+
return 0
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if check_qemu_system_aarch64; then
|
|
163
|
+
print "qemu-system-aarch64 is ready"
|
|
164
|
+
else
|
|
165
|
+
rc=$?
|
|
166
|
+
if [[ $rc -eq 1 ]]; then
|
|
167
|
+
print "qemu-system-aarch64 not found in installed packages"
|
|
168
|
+
else
|
|
169
|
+
print "qemu-system-aarch64 present but incomplete"
|
|
170
|
+
fi
|
|
171
|
+
|
|
172
|
+
# Try install from repo explicitly
|
|
173
|
+
print "Attempting to install qemu-system-aarch64 via dnf (best-effort)"
|
|
174
|
+
if dnf install -y qemu-system-aarch64 >/dev/null 2>&1; then
|
|
175
|
+
print "Installed qemu-system-aarch64 from repo"
|
|
176
|
+
else
|
|
177
|
+
print "qemu-system-aarch64 not available in enabled repos."
|
|
178
|
+
fi
|
|
179
|
+
|
|
180
|
+
if check_qemu_system_aarch64; then
|
|
181
|
+
print "qemu-system-aarch64 now available after package install"
|
|
182
|
+
else
|
|
183
|
+
print "Compiling QEMU with aarch64-softmmu target. Installing build deps..."
|
|
184
|
+
dnf groupinstall -y 'Development Tools' || true
|
|
185
|
+
dnf install -y git libaio-devel libgcrypt-devel libfdt-devel glib2-devel zlib-devel pixman-devel libseccomp-devel libusb1-devel openssl-devel bison flex python3 python3-pip ninja-build || true
|
|
186
|
+
|
|
187
|
+
# Enforce libslirp-devel for user networking
|
|
188
|
+
if ! dnf install -y libslirp-devel; then
|
|
189
|
+
err "Failed to install libslirp-devel. User networking will not work."
|
|
190
|
+
exit 1
|
|
191
|
+
fi
|
|
192
|
+
|
|
193
|
+
# Install required Python packages for QEMU build
|
|
194
|
+
print "Installing Python dependencies for QEMU build"
|
|
195
|
+
python3 -m pip install --upgrade pip || true
|
|
196
|
+
python3 -m pip install tomli meson || true
|
|
197
|
+
|
|
198
|
+
TMPDIR=$(mktemp -d)
|
|
199
|
+
print "Cloning QEMU source to $TMPDIR/qemu"
|
|
200
|
+
# Use a stable release tag (v9.0.0) to ensure consistency
|
|
201
|
+
git clone --depth 1 --branch v9.0.0 https://gitlab.com/qemu-project/qemu.git "$TMPDIR/qemu"
|
|
202
|
+
cd "$TMPDIR/qemu"
|
|
203
|
+
|
|
204
|
+
print "Configuring QEMU build"
|
|
205
|
+
if ./configure --target-list=aarch64-softmmu --enable-virtfs --enable-slirp; then
|
|
206
|
+
print "Configure successful, building QEMU..."
|
|
207
|
+
if make -j"$(nproc)"; then
|
|
208
|
+
print "Build successful, installing..."
|
|
209
|
+
make install || err "QEMU install failed"
|
|
210
|
+
# Update PATH to include /usr/local/bin where QEMU was installed
|
|
211
|
+
export PATH="/usr/local/bin:$PATH"
|
|
212
|
+
hash -r || true
|
|
213
|
+
else
|
|
214
|
+
err "QEMU build (make) failed"
|
|
215
|
+
fi
|
|
216
|
+
else
|
|
217
|
+
err "QEMU configure failed. Check dependencies."
|
|
218
|
+
fi
|
|
219
|
+
|
|
220
|
+
if check_qemu_system_aarch64; then
|
|
221
|
+
print "Successfully compiled and installed qemu-system-aarch64"
|
|
222
|
+
else
|
|
223
|
+
err "Compiled QEMU but qemu-system-aarch64 still missing or lacks 'virt'. Check logs in $TMPDIR/qemu"
|
|
224
|
+
fi
|
|
225
|
+
|
|
226
|
+
cd /
|
|
227
|
+
rm -rf "$TMPDIR" || true
|
|
228
|
+
print "Removed build directory $TMPDIR"
|
|
229
|
+
fi
|
|
46
230
|
fi
|
|
47
231
|
|
|
48
|
-
#
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
232
|
+
# 7) Summary and verification commands for the user
|
|
233
|
+
print "\n=== Summary / Quick verification commands ==="
|
|
234
|
+
if command -v packer >/dev/null 2>&1; then print "packer: $(command -v packer)"; else print "packer: NOT INSTALLED"; fi
|
|
235
|
+
# Check /usr/local/bin explicitly for compiled qemu
|
|
236
|
+
if [ -x /usr/local/bin/qemu-system-aarch64 ]; then
|
|
237
|
+
print "qemu-system-aarch64: /usr/local/bin/qemu-system-aarch64"
|
|
238
|
+
elif command -v qemu-system-aarch64 >/dev/null 2>&1; then
|
|
239
|
+
print "qemu-system-aarch64: $(command -v qemu-system-aarch64)"
|
|
240
|
+
else
|
|
241
|
+
print "qemu-system-aarch64: NOT INSTALLED"
|
|
52
242
|
fi
|
|
243
|
+
if command -v qemu-aarch64-static >/dev/null 2>&1; then print "qemu-aarch64-static: $(command -v qemu-aarch64-static)"; else print "qemu-aarch64-static: NOT INSTALLED"; fi
|
|
244
|
+
print "libvirtd status:"
|
|
245
|
+
systemctl status libvirtd --no-pager || true
|
|
246
|
+
|
|
247
|
+
cat <<'EOF'
|
|
248
|
+
|
|
249
|
+
=== Example Packer qemu builder snippets ===
|
|
250
|
+
|
|
251
|
+
# aarch64 on x86_64 host (use qemu-system-aarch64 with TCG):
|
|
252
|
+
{
|
|
253
|
+
"type": "qemu",
|
|
254
|
+
"qemu_binary": "/usr/local/bin/qemu-system-aarch64",
|
|
255
|
+
"accelerator": "tcg",
|
|
256
|
+
"format": "raw",
|
|
257
|
+
"disk_size": "8192",
|
|
258
|
+
"headless": true,
|
|
259
|
+
"qemuargs": [
|
|
260
|
+
["-machine", "virt,highmem=on"],
|
|
261
|
+
["-cpu", "cortex-a57"],
|
|
262
|
+
["-bios", "/usr/share/edk2-aarch64/QEMU_EFI.fd"],
|
|
263
|
+
["-device", "virtio-net-device,netdev=net0"],
|
|
264
|
+
["-netdev", "user,id=net0,hostfwd=tcp::2222-:22"]
|
|
265
|
+
]
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
# x86_64 on arm64 host (use kvm when available):
|
|
269
|
+
{
|
|
270
|
+
"type": "qemu",
|
|
271
|
+
"qemu_binary": "/usr/bin/qemu-system-x86_64",
|
|
272
|
+
"accelerator": "kvm",
|
|
273
|
+
"format": "raw",
|
|
274
|
+
"disk_size": "8192",
|
|
275
|
+
"headless": true,
|
|
276
|
+
"qemuargs": [
|
|
277
|
+
["-machine", "pc,q35"],
|
|
278
|
+
["-cpu", "host"],
|
|
279
|
+
["-bios", "/usr/share/ovmf/OVMF_CODE.fd"],
|
|
280
|
+
["-device", "virtio-net-pci,netdev=net0"],
|
|
281
|
+
["-netdev", "user,id=net0,hostfwd=tcp::2223-:22"]
|
|
282
|
+
]
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
EOF
|
|
286
|
+
|
|
287
|
+
print "Done. If any package failed to install due to repo availability, consider enabling CRB/powertools, adding a trusted COPR or rebuild repo for qemu, or compiling QEMU locally as the script offered."
|
|
288
|
+
|
|
289
|
+
exit 0
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
BASHRC="$HOME/.bashrc"
|
|
5
|
+
|
|
6
|
+
# Check whether a "ports" function is already defined
|
|
7
|
+
if grep -Eq '^\s*(function\s+ports|ports\s*\(\))' "$BASHRC"; then
|
|
8
|
+
echo "The 'ports' function already exists in $BASHRC. Nothing was changed."
|
|
9
|
+
exit 0
|
|
10
|
+
fi
|
|
11
|
+
|
|
12
|
+
# Append the function to the end of ~/.bashrc
|
|
13
|
+
cat >> "$BASHRC" <<'EOF'
|
|
14
|
+
|
|
15
|
+
# >>> ports function added by script >>>
|
|
16
|
+
ports() {
|
|
17
|
+
# no arguments: show listening TCP+UDP sockets
|
|
18
|
+
if [ -z "$1" ]; then
|
|
19
|
+
sudo ss -ltnup
|
|
20
|
+
return
|
|
21
|
+
fi
|
|
22
|
+
|
|
23
|
+
# with an argument: print the header and lines that exactly match the given port
|
|
24
|
+
local p="$1"
|
|
25
|
+
sudo ss -tunap | awk -v p="$p" 'NR==1 || $0 ~ (":"p"($| )")'
|
|
26
|
+
}
|
|
27
|
+
# <<< ports function added by script <<<
|
|
28
|
+
EOF
|
|
29
|
+
|
|
30
|
+
echo "Function 'ports' was added to $BASHRC."
|
|
31
|
+
echo "Load it now with: source ~/.bashrc (or open a new terminal)."
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
url="${1:-}"
|
|
5
|
+
[[ "$url" =~ ^tftp://([^/]+)(/.+)$ ]] || { echo "Usage: $0 tftp://host/path"; exit 2; }
|
|
6
|
+
host="${BASH_REMATCH[1]}"
|
|
7
|
+
path="${BASH_REMATCH[2]}"
|
|
8
|
+
outfile="/tmp/$(basename "$path")"
|
|
9
|
+
|
|
10
|
+
if command -v curl >/dev/null 2>&1; then
|
|
11
|
+
curl -f --silent --output "$outfile" "$url" && echo "OK: $outfile ($(stat -c%s "$outfile") bytes)" || { echo "curl: failed"; exit 3; }
|
|
12
|
+
elif command -v tftp >/dev/null 2>&1; then
|
|
13
|
+
printf "get %s %s\nquit\n" "$path" "$outfile" | tftp "$host" \
|
|
14
|
+
&& [[ -s "$outfile" ]] \
|
|
15
|
+
&& echo "OK: $outfile ($(stat -c%s "$outfile") bytes)" \
|
|
16
|
+
|| { echo "tftp: failed"; exit 3; }
|
|
17
|
+
else
|
|
18
|
+
echo "Install 'curl' or 'tftp-client' (sudo dnf install -y curl tftp-client)"; exit 4
|
|
19
|
+
fi
|
|
@@ -61,6 +61,21 @@ const DocumentController = {
|
|
|
61
61
|
});
|
|
62
62
|
}
|
|
63
63
|
},
|
|
64
|
+
patch: async (req, res, options) => {
|
|
65
|
+
try {
|
|
66
|
+
const result = await DocumentService.patch(req, res, options);
|
|
67
|
+
return res.status(200).json({
|
|
68
|
+
status: 'success',
|
|
69
|
+
data: result,
|
|
70
|
+
});
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logger.error(error, error.stack);
|
|
73
|
+
return res.status(400).json({
|
|
74
|
+
status: 'error',
|
|
75
|
+
message: error.message,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
},
|
|
64
79
|
};
|
|
65
80
|
|
|
66
81
|
export { DocumentController };
|
|
@@ -19,6 +19,16 @@ const DocumentSchema = new Schema(
|
|
|
19
19
|
type: Schema.Types.ObjectId,
|
|
20
20
|
ref: 'File',
|
|
21
21
|
},
|
|
22
|
+
share: {
|
|
23
|
+
copyShareLinkEvent: [
|
|
24
|
+
{
|
|
25
|
+
year: { type: Number },
|
|
26
|
+
month: { type: Number },
|
|
27
|
+
day: { type: Number },
|
|
28
|
+
count: { type: Number, default: 0 },
|
|
29
|
+
},
|
|
30
|
+
],
|
|
31
|
+
},
|
|
22
32
|
},
|
|
23
33
|
{
|
|
24
34
|
timestamps: true,
|
|
@@ -53,6 +63,10 @@ const DocumentDto = {
|
|
|
53
63
|
};
|
|
54
64
|
},
|
|
55
65
|
},
|
|
66
|
+
getTotalCopyShareLinkCount: (document) => {
|
|
67
|
+
if (!document.share || !document.share.copyShareLinkEvent) return 0;
|
|
68
|
+
return document.share.copyShareLinkEvent.reduce((total, event) => total + (event.count || 0), 0);
|
|
69
|
+
},
|
|
56
70
|
};
|
|
57
71
|
|
|
58
72
|
export { DocumentSchema, DocumentModel, ProviderSchema, DocumentDto };
|
|
@@ -14,6 +14,7 @@ const DocumentRouter = (options) => {
|
|
|
14
14
|
router.get(`/`, authMiddleware, async (req, res) => await DocumentController.get(req, res, options));
|
|
15
15
|
router.put(`/:id`, authMiddleware, async (req, res) => await DocumentController.put(req, res, options));
|
|
16
16
|
router.put(`/`, authMiddleware, async (req, res) => await DocumentController.put(req, res, options));
|
|
17
|
+
router.patch(`/:id/copy-share-link`, async (req, res) => await DocumentController.patch(req, res, options));
|
|
17
18
|
router.delete(`/:id`, authMiddleware, async (req, res) => await DocumentController.delete(req, res, options));
|
|
18
19
|
router.delete(`/`, authMiddleware, async (req, res) => await DocumentController.delete(req, res, options));
|
|
19
20
|
return router;
|
|
@@ -64,20 +64,39 @@ const DocumentService = {
|
|
|
64
64
|
const lastDoc = await Document.findOne(queryPayload, '_id').sort({ createdAt: 1 });
|
|
65
65
|
const lastId = lastDoc ? lastDoc._id : null;
|
|
66
66
|
|
|
67
|
+
// Add totalCopyShareLinkCount to each document
|
|
68
|
+
const dataWithCounts = data.map((doc) => {
|
|
69
|
+
const docObj = doc.toObject ? doc.toObject() : doc;
|
|
70
|
+
return {
|
|
71
|
+
...docObj,
|
|
72
|
+
totalCopyShareLinkCount: DocumentDto.getTotalCopyShareLinkCount(doc),
|
|
73
|
+
};
|
|
74
|
+
});
|
|
75
|
+
|
|
67
76
|
return {
|
|
68
|
-
data,
|
|
77
|
+
data: dataWithCounts,
|
|
69
78
|
lastId,
|
|
70
79
|
};
|
|
71
80
|
}
|
|
72
81
|
|
|
73
82
|
switch (req.params.id) {
|
|
74
|
-
default:
|
|
75
|
-
|
|
83
|
+
default: {
|
|
84
|
+
const data = await Document.find({
|
|
76
85
|
userId: req.auth.user._id,
|
|
77
86
|
...(req.params.id ? { _id: req.params.id } : undefined),
|
|
78
87
|
})
|
|
79
88
|
.populate(DocumentDto.populate.file())
|
|
80
89
|
.populate(DocumentDto.populate.mdFile());
|
|
90
|
+
|
|
91
|
+
// Add totalCopyShareLinkCount to each document
|
|
92
|
+
return data.map((doc) => {
|
|
93
|
+
const docObj = doc.toObject ? doc.toObject() : doc;
|
|
94
|
+
return {
|
|
95
|
+
...docObj,
|
|
96
|
+
totalCopyShareLinkCount: DocumentDto.getTotalCopyShareLinkCount(doc),
|
|
97
|
+
};
|
|
98
|
+
});
|
|
99
|
+
}
|
|
81
100
|
}
|
|
82
101
|
},
|
|
83
102
|
delete: async (req, res, options) => {
|
|
@@ -131,6 +150,45 @@ const DocumentService = {
|
|
|
131
150
|
}
|
|
132
151
|
}
|
|
133
152
|
},
|
|
153
|
+
patch: async (req, res, options) => {
|
|
154
|
+
/** @type {import('./document.model.js').DocumentModel} */
|
|
155
|
+
const Document = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.Document;
|
|
156
|
+
|
|
157
|
+
if (req.path.includes('/copy-share-link')) {
|
|
158
|
+
const document = await Document.findById(req.params.id);
|
|
159
|
+
if (!document) throw new Error('Document not found');
|
|
160
|
+
|
|
161
|
+
const now = new Date();
|
|
162
|
+
const year = now.getFullYear();
|
|
163
|
+
const month = now.getMonth() + 1; // 0-indexed
|
|
164
|
+
const day = now.getDate();
|
|
165
|
+
|
|
166
|
+
// Find existing entry for this year/month/day
|
|
167
|
+
const existingEventIndex = document.share?.copyShareLinkEvent?.findIndex(
|
|
168
|
+
(event) => event.year === year && event.month === month && event.day === day,
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (existingEventIndex !== undefined && existingEventIndex >= 0) {
|
|
172
|
+
// Increment existing count
|
|
173
|
+
document.share.copyShareLinkEvent[existingEventIndex].count += 1;
|
|
174
|
+
} else {
|
|
175
|
+
// Create new entry
|
|
176
|
+
if (!document.share) document.share = {};
|
|
177
|
+
if (!document.share.copyShareLinkEvent) document.share.copyShareLinkEvent = [];
|
|
178
|
+
document.share.copyShareLinkEvent.push({
|
|
179
|
+
year,
|
|
180
|
+
month,
|
|
181
|
+
day,
|
|
182
|
+
count: 1,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
await document.save();
|
|
187
|
+
return document;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
throw new Error('Invalid patch endpoint');
|
|
191
|
+
},
|
|
134
192
|
};
|
|
135
193
|
|
|
136
194
|
export { DocumentService };
|