Build the image from the vendor vshell-https .deb on ubuntu:24.04 (required: GLIBC_2.38 / GLIBCXX_3.4.32). Contains the Dockerfile, the entrypoint that starts and supervises the three VShell daemons without systemd, the no-container-runtime build/validate/push tooling used to publish the image, and the vendored .deb (sha256 pinned). Published as gitea.milesward.dev/mward4/vshell:5.0.1-3985 and :latest. Manifest digest sha256:8b0ff00be981fa05f1e4a4e4ff5307b6c7e5e61b487d4fbe7e02fefb2a04659f.
102 lines
3.6 KiB
Bash
Executable File
102 lines
3.6 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Validate a VShell container rootfs without a container runtime.
|
|
#
|
|
# Runs the vendor binaries, checks the configuration, then starts the image's
|
|
# real entrypoint (docker/entrypoint.sh) inside a user-namespace chroot on
|
|
# high ports and probes every listener from the outside.
|
|
#
|
|
# Usage: tools/validate.sh [/tmp/vshell-rootfs]
|
|
#
|
|
# Exit status 0 means every check passed.
|
|
set -uo pipefail
|
|
|
|
ROOTFS=${1:-/tmp/vshell-rootfs}
|
|
CFG=/tmp/validate_config
|
|
CERT=/tmp/validate.p12
|
|
SSH_PORT=2222 FTPS_PORT=2121 FTPSI_PORT=2990 HTTP_PORT=8080 HTTPS_PORT=8443
|
|
|
|
FAILED=0
|
|
say() { printf '%s\n' "$*"; }
|
|
fail() { printf 'FAIL: %s\n' "$*"; FAILED=1; }
|
|
|
|
[ -x "$ROOTFS/usr/local/sbin/vshelld" ] || { say "no VShell install under $ROOTFS"; exit 1; }
|
|
rm -f "$ROOTFS$CFG" "$ROOTFS$CERT"
|
|
|
|
# The chroot needs /dev entries for the daemons and openssl to behave normally.
|
|
in_ns() {
|
|
unshare -rm sh -c "
|
|
for d in null zero random urandom tty; do
|
|
[ -e $ROOTFS/dev/\$d ] || touch $ROOTFS/dev/\$d
|
|
mount --bind /dev/\$d $ROOTFS/dev/\$d
|
|
done
|
|
$1
|
|
"
|
|
}
|
|
|
|
say '== binaries, configuration and license'
|
|
in_ns "chroot $ROOTFS /bin/bash -c '
|
|
set -e
|
|
/usr/local/sbin/vshelld -version
|
|
/usr/local/sbin/vshell-httpsd -version
|
|
/usr/local/sbin/vshell-ftpsd -version
|
|
/usr/local/sbin/vshelld -test_config -f /usr/local/etc/vshelld_config
|
|
/usr/local/sbin/vshelld -license
|
|
'" || fail 'version/config/license checks'
|
|
|
|
say '== starting services through the image entrypoint'
|
|
in_ns "chroot $ROOTFS /bin/bash -c '
|
|
exec env VSHELL_CONFIG=$CFG VSHELL_TLS_P12=$CERT \
|
|
VSHELL_SSH_ADDR=0.0.0.0:$SSH_PORT VSHELL_HTTPS_ADDR=0.0.0.0:$HTTPS_PORT \
|
|
VSHELL_HTTP_ADDR=0.0.0.0:$HTTP_PORT VSHELL_FTPS_EXPLICIT_ADDR=0.0.0.0:$FTPS_PORT \
|
|
VSHELL_FTPS_IMPLICIT_ADDR=0.0.0.0:$FTPSI_PORT \
|
|
/usr/local/bin/vshell-entrypoint
|
|
'" >/tmp/vshell-validate.log 2>&1 &
|
|
sleep 8
|
|
sed 's/^/ entrypoint: /' /tmp/vshell-validate.log
|
|
|
|
banner() { # port, label
|
|
local port=$1 label=$2 line
|
|
if ! exec 3<>"/dev/tcp/127.0.0.1/$port" 2>/dev/null; then fail "$label ($port): connection refused"; return; fi
|
|
if read -t 3 -r line <&3; then say "ok $label ($port): $line"; else fail "$label ($port): no banner"; fi
|
|
exec 3<&- 3>&-
|
|
}
|
|
|
|
banner "$SSH_PORT" 'SSH '
|
|
banner "$FTPS_PORT" 'FTPS '
|
|
|
|
http_probe() {
|
|
local port=$1 line
|
|
if ! exec 3<>"/dev/tcp/127.0.0.1/$port" 2>/dev/null; then fail "HTTP ($port): connection refused"; return; fi
|
|
printf 'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' >&3
|
|
if read -t 4 -r line <&3; then say "ok HTTP ($port): $line"; else fail "HTTP ($port): no response"; fi
|
|
exec 3<&- 3>&-
|
|
}
|
|
http_probe "$HTTP_PORT"
|
|
|
|
if command -v node >/dev/null; then
|
|
node -e '
|
|
const tls = require("tls");
|
|
const port = Number(process.argv[1]);
|
|
const s = tls.connect({ port, host: "127.0.0.1", rejectUnauthorized: false }, () => {
|
|
const c = s.getPeerCertificate();
|
|
console.log(`ok TLS (${port}): ${s.getProtocol()} CN=${c.subject && c.subject.CN}`);
|
|
s.destroy();
|
|
});
|
|
s.on("error", (e) => { console.log(`FAIL: TLS (${port}): ${e.message}`); process.exitCode = 1; });
|
|
s.setTimeout(6000, () => { console.log(`FAIL: TLS (${port}): timeout`); s.destroy(); process.exitCode = 1; });
|
|
' "$HTTPS_PORT" || FAILED=1
|
|
else
|
|
say 'skip TLS probe (node not available)'
|
|
fi
|
|
|
|
say '== stopping'
|
|
for p in $(ls /proc | grep -E '^[0-9]+$'); do
|
|
[ -r "/proc/$p/cmdline" ] || continue
|
|
c=$(tr '\0' ' ' <"/proc/$p/cmdline" 2>/dev/null) || continue
|
|
case "$c" in /usr/local/sbin/*) kill "$p" 2>/dev/null && say " killed pid $p";; esac
|
|
done
|
|
wait 2>/dev/null
|
|
|
|
if [ "$FAILED" -eq 0 ]; then say '== ALL CHECKS PASSED'; else say '== VALIDATION FAILED'; fi
|
|
exit "$FAILED"
|