Build rust image with full toolchain, workflow, and utilities

Install latest stable Rust at build time via rustup-init with SHA256
verification. Add 30 cross-compile targets, cargo-binstall, and a
comprehensive set of cargo development tools. Match the official
rust:alpine env-var convention (RUSTUP_HOME / CARGO_HOME) and declare
those paths as Docker VOLUMEs. Add the Go-image pattern: rust-workflow
runs automatically when the container is invoked with no args, while
explicit commands pass through unchanged. Copy language-agnostic
healthcheck, copy, and symlink utilities from the Go image.
- Dockerfile: WORKDIR /app in final stage; add RUSTUP_HOME, CARGO_HOME,
RUSTUP_TOOLCHAIN ENV vars; extend VOLUME to include cargo and rustup paths
- rootfs/root/docker/setup/05-custom.sh: full Rust toolchain install —
build deps (build-base musl-dev clang lld cmake openssl-dev), SHA256-
verified rustup-init, stable toolchain with rust-src/rust-analyzer/
llvm-tools-preview, 30 cross-compile targets, cargo-binstall bootstrap,
cargo tool suite via binstall, cross-linker config.toml, /usr/local/bin
symlinks, ~/.cargo and ~/.rustup home symlinks, /etc/profile.d/rust.sh
- rootfs/usr/local/bin/rust-workflow: default workflow script —
fmt --check → clippy -D warnings → test --all → build --release;
honours CARGO_WORKDIR and CARGO_BUILD_TARGET env vars
- rootfs/usr/local/bin/entrypoint.sh: __no_exit guarded by $# -eq 0 in
START_SERVICES block; * catch-all now calls rust-workflow on no args
- rootfs/usr/local/bin/healthcheck: copied from Go image (HTTP/TCP/
process/file health probe)
- rootfs/usr/local/bin/copy: copied from Go image (recursive copy utility)
- rootfs/usr/local/bin/symlink: copied from Go image (symlink utility)

Dockerfile
rootfs/root/docker/setup/05-custom.sh
rootfs/usr/local/bin/copy
rootfs/usr/local/bin/entrypoint.sh
rootfs/usr/local/bin/healthcheck
rootfs/usr/local/bin/rust-workflow
rootfs/usr/local/bin/symlink
This commit is contained in:
2026-05-31 11:33:51 -04:00
parent 4143ac6d72
commit e36a6888dd
7 changed files with 744 additions and 13 deletions
+5 -2
View File
@@ -207,7 +207,7 @@ ARG LICENSE="WTFPL"
ARG ENV_PORTS="${EXPOSE_PORTS}" ARG ENV_PORTS="${EXPOSE_PORTS}"
USER ${USER} USER ${USER}
WORKDIR /root WORKDIR /app
LABEL maintainer="CasjaysDev <docker-admin@casjaysdev.pro>" LABEL maintainer="CasjaysDev <docker-admin@casjaysdev.pro>"
LABEL org.opencontainers.image.vendor="CasjaysDev" LABEL org.opencontainers.image.vendor="CasjaysDev"
@@ -245,10 +245,13 @@ ENV NODE_MANAGER="${NODE_MANAGER}"
ENV PHP_VERSION="${PHP_VERSION}" ENV PHP_VERSION="${PHP_VERSION}"
ENV DISTRO_VERSION="${IMAGE_VERSION}" ENV DISTRO_VERSION="${IMAGE_VERSION}"
ENV WWW_ROOT_DIR="${WWW_ROOT_DIR}" ENV WWW_ROOT_DIR="${WWW_ROOT_DIR}"
ENV RUSTUP_HOME="/usr/local/share/rustup"
ENV CARGO_HOME="/usr/local/share/cargo"
ENV RUSTUP_TOOLCHAIN="stable"
COPY --from=build /. / COPY --from=build /. /
VOLUME [ "/config","/data" ] VOLUME [ "/config","/data","/usr/local/share/cargo","/usr/local/share/rustup" ]
EXPOSE ${SERVICE_PORT} ${ENV_PORTS} EXPOSE ${SERVICE_PORT} ${ENV_PORTS}
+240 -3
View File
@@ -8,9 +8,8 @@
# @@Copyright : Copyright 2026 CasjaysDev # @@Copyright : Copyright 2026 CasjaysDev
# @@Created : Sun May 31 11:04:50 AM EDT 2026 # @@Created : Sun May 31 11:04:50 AM EDT 2026
# @@File : 05-custom.sh # @@File : 05-custom.sh
# @@Description : script to run custom # @@Description : Install latest stable Rust toolchain with static-build tooling
# @@Changelog : newScript # @@Changelog : newScript
# @@TODO : Refactor code
# @@Other : N/A # @@Other : N/A
# @@Resource : N/A # @@Resource : N/A
# @@Terminal App : yes # @@Terminal App : yes
@@ -32,6 +31,245 @@ exitCode=0
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Main script # Main script
# Install C/C++ toolchain and static-build dependencies needed by Rust -sys crates
pkmgr install build-base musl-dev clang lld cmake make perl openssl-dev pkgconf
# Install cross-compile toolchains — failures are non-fatal on minimal mirrors
pkmgr install mingw-w64-gcc || true
# Install zig for cargo-zigbuild (C-dep cross-compilation without a sysroot)
pkmgr install zig || true
# Install binaryen (wasm-opt) for WASM size optimisation tooling
pkmgr install binaryen || true
# - - - - - - - - - - - - - - - - - - - - - - - - -
# rustup paths — match the official rust:alpine convention so existing
# workflows work unchanged; volumes are declared at these paths in the Dockerfile
export RUSTUP_HOME="/usr/local/share/rustup"
export CARGO_HOME="/usr/local/share/cargo"
export PATH="$CARGO_HOME/bin:$PATH"
mkdir -p "$RUSTUP_HOME" "$CARGO_HOME/bin"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Download the architecture-appropriate rustup-init and verify its SHA256
# before executing — this is the only thing that ever touches rust-lang.org
# directly; all subsequent installs go through rustup or cargo-binstall
RUSTUP_ARCH="$(uname -m)-unknown-linux-musl"
RUSTUP_URL="https://static.rust-lang.org/rustup/dist/${RUSTUP_ARCH}/rustup-init"
curl -sSfL "$RUSTUP_URL" -o /tmp/rustup-init
curl -sSfL "${RUSTUP_URL}.sha256" -o /tmp/rustup-init.sha256
EXPECTED_SHA="$(awk '{print $1}' /tmp/rustup-init.sha256)"
ACTUAL_SHA="$(sha256sum /tmp/rustup-init | awk '{print $1}')"
[ "$EXPECTED_SHA" = "$ACTUAL_SHA" ] || {
echo "rustup-init SHA256 mismatch: expected $EXPECTED_SHA got $ACTUAL_SHA" >&2
exit 1
}
chmod +x /tmp/rustup-init
# Install the latest stable toolchain; --profile default includes rustfmt and clippy
/tmp/rustup-init -y --no-modify-path \
--default-toolchain stable \
--profile default
rm -f /tmp/rustup-init /tmp/rustup-init.sha256
# Add components not included in the default profile
rustup component add rust-src rust-analyzer llvm-tools-preview
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Cross-compile targets — Linux musl (fully static, no libc dependency)
rustup target add \
x86_64-unknown-linux-musl \
aarch64-unknown-linux-musl \
i686-unknown-linux-musl \
armv7-unknown-linux-musleabihf \
riscv64gc-unknown-linux-musl
# Cross-compile targets — Linux glibc
rustup target add \
x86_64-unknown-linux-gnu \
aarch64-unknown-linux-gnu \
i686-unknown-linux-gnu \
armv7-unknown-linux-gnueabihf \
arm-unknown-linux-gnueabi \
riscv64gc-unknown-linux-gnu \
powerpc64le-unknown-linux-gnu \
s390x-unknown-linux-gnu
# Cross-compile targets — Windows GNU ABI (MSVC ABI is not supported)
rustup target add \
x86_64-pc-windows-gnu \
i686-pc-windows-gnu \
aarch64-pc-windows-gnullvm
# Cross-compile targets — macOS (pure-Rust only; Apple SDK not bundled)
rustup target add \
x86_64-apple-darwin \
aarch64-apple-darwin
# Cross-compile targets — BSD
rustup target add \
x86_64-unknown-freebsd
# Cross-compile targets — WebAssembly
rustup target add \
wasm32-unknown-unknown \
wasm32-wasip1 \
wasm32-wasip2 \
wasm32-unknown-emscripten
# Cross-compile targets — Embedded ARM (require no_std source)
rustup target add \
thumbv6m-none-eabi \
thumbv7em-none-eabihf \
thumbv8m.main-none-eabi
# Cross-compile targets — Embedded RISC-V (require no_std source)
rustup target add \
riscv32imc-unknown-none-elf \
riscv32imac-unknown-none-elf
# Cross-compile targets — Android
rustup target add \
aarch64-linux-android
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Bootstrap cargo-binstall — downloads prebuilt binaries instead of
# compiling every tool from source, cutting install time dramatically
BINSTALL_ARCH="$(uname -m)"
BINSTALL_URL="https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-${BINSTALL_ARCH}-unknown-linux-musl.tgz"
curl -sSfL "$BINSTALL_URL" -o /tmp/cargo-binstall.tgz
tar xzf /tmp/cargo-binstall.tgz -C /tmp cargo-binstall
install -m 755 /tmp/cargo-binstall "$CARGO_HOME/bin/cargo-binstall"
rm -f /tmp/cargo-binstall.tgz /tmp/cargo-binstall
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Workflow and development tools — all have musl prebuilt binaries
cargo binstall -y \
cargo-edit \
cargo-watch \
cargo-update \
cargo-outdated \
cargo-expand \
cargo-info \
bacon \
cargo-nextest \
cargo-llvm-cov \
cargo-tarpaulin \
cargo-audit \
cargo-deny \
cargo-machete \
cargo-semver-checks \
cargo-make \
cargo-deb \
cargo-generate \
cargo-release \
cargo-chef \
cargo-zigbuild \
just \
tokei \
hyperfine \
wasm-pack \
wasm-tools \
wasm-bindgen-cli \
cbindgen \
cargo-binutils \
cargo-bloat \
cargo-asm \
mdbook \
mdbook-toc
# Tools that occasionally lack musl prebuilts — fall back to source compilation
cargo binstall -y cargo-dist 2>/dev/null || cargo install cargo-dist
cargo binstall -y cargo-msrv 2>/dev/null || cargo install cargo-msrv
cargo binstall -y cargo-mutants 2>/dev/null || cargo install cargo-mutants
cargo binstall -y flip-link 2>/dev/null || cargo install flip-link
cargo binstall -y cargo-ndk 2>/dev/null || cargo install cargo-ndk
cargo binstall -y trunk 2>/dev/null || cargo install trunk 2>/dev/null || true
# cross (the cross-rs cross-compilation runner)
cargo binstall -y cross 2>/dev/null || cargo install cross 2>/dev/null || true
# probe-rs requires the cli feature flag and is best built from source
cargo install probe-rs --features cli 2>/dev/null || true
# samply and cargo-flamegraph require a system perf or dtrace — best-effort
cargo binstall -y samply 2>/dev/null || true
cargo binstall -y cargo-flamegraph 2>/dev/null || true
# sqlx-cli and sea-orm-cli need project-specific feature flags at runtime;
# install a broadly compatible build here as a convenience
cargo install sqlx-cli --no-default-features --features native-tls,postgres,mysql,sqlite 2>/dev/null || true
cargo install sea-orm-cli 2>/dev/null || true
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Linker configuration for cross-compilation targets.
# rust-lld handles ARM/RISC-V/embedded; mingw-w64 gcc handles Windows GNU.
mkdir -p "$CARGO_HOME"
cat > "$CARGO_HOME/config.toml" << 'CARGOCONFIG'
[target.aarch64-unknown-linux-musl]
linker = "rust-lld"
[target.armv7-unknown-linux-musleabihf]
linker = "rust-lld"
[target.armv7-unknown-linux-gnueabihf]
linker = "rust-lld"
[target.arm-unknown-linux-gnueabi]
linker = "rust-lld"
[target.thumbv6m-none-eabi]
linker = "rust-lld"
[target.thumbv7em-none-eabihf]
linker = "rust-lld"
[target.thumbv8m.main-none-eabi]
linker = "rust-lld"
[target.riscv32imc-unknown-none-elf]
linker = "rust-lld"
[target.riscv32imac-unknown-none-elf]
linker = "rust-lld"
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
ar = "x86_64-w64-mingw32-ar"
[target.i686-pc-windows-gnu]
linker = "i686-w64-mingw32-gcc"
ar = "i686-w64-mingw32-ar"
CARGOCONFIG
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Symlink every cargo/bin tool into /usr/local/bin so they are reachable
# without a login shell — the Dockerfile's PATH includes /usr/local/bin
for bin_file in "$CARGO_HOME/bin"/*; do
[ -x "$bin_file" ] || continue
ln -sf "$bin_file" "/usr/local/bin/${bin_file##*/}"
done
# Home-relative symlinks expected by rustup, cargo, and most documentation
ln -sf "$CARGO_HOME" "/root/.cargo"
ln -sf "$RUSTUP_HOME" "/root/.rustup"
# Profile.d entry so login shells get the full environment
cat > /etc/profile.d/rust.sh << PROFILE
export RUSTUP_HOME="${RUSTUP_HOME}"
export CARGO_HOME="${CARGO_HOME}"
export RUSTUP_TOOLCHAIN="stable"
export PATH="${CARGO_HOME}/bin:\${PATH}"
PROFILE
# Work directories mounted or referenced in the README
mkdir -p /app /work /root/app /root/project /data/build
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# Set the exit code # Set the exit code
exitCode=$? exitCode=$?
@@ -40,4 +278,3 @@ exit $exitCode
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
# ex: ts=2 sw=2 et filetype=sh # ex: ts=2 sw=2 et filetype=sh
# - - - - - - - - - - - - - - - - - - - - - - - - - # - - - - - - - - - - - - - - - - - - - - - - - - -
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env sh
# shellcheck shell=sh
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202605051306-git
# @@Author : Jason Hempstead
# @@Contact : jason@casjaysdev.pro
# @@License : WTFPL
# @@ReadME : copy --help
# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments
# @@Created : Tuesday, May 05, 2026 13:06 EDT
# @@File : copy
# @@Description : copies a file and shows progress
# @@Changelog : Refactored for self-contained operation
# @@TODO : Better documentation
# @@Other :
# @@Resource :
# @@Terminal App : no
# @@sudo/root : no
# @@Template : shell/sh
# - - - - - - - - - - - - - - - - - - - - - - - - -
# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329
# - - - - - - - - - - - - - - - - - - - - - - - - -
APPNAME="$(basename -- "$0" 2>/dev/null)"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# colorization
if [ -n "$NO_COLOR" ]; then
__printf_color() { printf '%b' "$1\n" | tr -d '\t' | sed '/^%b$/d;s,\x1B\[ 0-9;]*[a-zA-Z],,g'; }
else
__printf_color() { { [ -z "$2" ] || DEFAULT_COLOR=$2; } && printf "%b" "$(tput setaf "$DEFAULT_COLOR" 2>/dev/null)" "$1\n" "$(tput sgr0 2>/dev/null)"; }
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
__unlink() { [ -L "$1" ] && rm -f -- "$1" >/dev/null; }
# - - - - - - - - - - - - - - - - - - - - - - - - -
# custom functions
__copy() {
exitCode=0
if [ -d "$1" ]; then
__printf_color "Copying $1/* to $2/"
__unlink "$2"
mkdir -p "$2"
for f in "$1"/* "$1"/.[!.]* "$1"/..?*; do
[ -e "$f" ] || [ -L "$f" ] || continue
base=$(basename -- "$f")
__copy "$f" "$2/$base" || exitCode=$?
done
elif [ -f "$1" ] || [ -L "$1" ]; then
__printf_color "Copying $1 to $2"
__unlink "$2"
cp -Rf "$1" "$2"
exitCode=$?
fi
return $exitCode
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Define variables
DEFAULT_COLOR="254"
COPY_EXIT_STATUS=0
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Main application
if [ $# -ne 2 ]; then
__printf_color "USAGE: $APPNAME to from" "1" >&2
COPY_EXIT_STATUS=1
elif [ ! -e "$1" ]; then
__printf_color "$1 does not exist" >&2
COPY_EXIT_STATUS=2
else
__printf_color "Copying $1 to $2" "4"
__copy "$1" "$2" >/dev/null
COPY_EXIT_STATUS=$?
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
# End application
# - - - - - - - - - - - - - - - - - - - - - - - - -
# lets exit with code
# - - - - - - - - - - - - - - - - - - - - - - - - -
exit $COPY_EXIT_STATUS
# - - - - - - - - - - - - - - - - - - - - - - - - -
# ex: ts=2 sw=2 et filetype=sh
+7 -8
View File
@@ -506,9 +506,11 @@ if [ "$START_SERVICES" = "yes" ] || [ -z "$1" ]; then
echo "$$" >"$ENTRYPOINT_PID_FILE" echo "$$" >"$ENTRYPOINT_PID_FILE"
__start_init_scripts "/usr/local/etc/docker/init.d" __start_init_scripts "/usr/local/etc/docker/init.d"
CONTAINER_INIT="${CONTAINER_INIT:-no}" CONTAINER_INIT="${CONTAINER_INIT:-no}"
# Services started successfully - enter monitoring mode # Only block in monitoring mode when no user command was given
__no_exit if [ $# -eq 0 ]; then
exit $? __no_exit
exit $?
fi
fi fi
START_SERVICES="no" START_SERVICES="no"
fi fi
@@ -674,11 +676,8 @@ start)
# Execute primary command # Execute primary command
*) *)
if [ $# -eq 0 ]; then if [ $# -eq 0 ]; then
if [ ! -f "$ENTRYPOINT_PID_FILE" ]; then # No args: run the default Rust workflow (fmt → clippy → test → build)
echo "$$" >"$ENTRYPOINT_PID_FILE" __exec_command rust-workflow
[ "$START_SERVICES" = "no" ] && [ "$CONTAINER_INIT" = "yes" ] || __start_init_scripts "/usr/local/etc/docker/init.d"
fi
__no_exit
else else
__exec_command "$@" __exec_command "$@"
fi fi
+249
View File
@@ -0,0 +1,249 @@
#!/usr/bin/env sh
# shellcheck shell=sh
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202605051654-git
# @@Author : Jason Hempstead
# @@Contact : jason@casjaysdev.pro
# @@License : WTFPL
# @@ReadME : healthcheck --help
# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments
# @@Created : Tuesday, May 05, 2026 16:54 EDT
# @@File : healthcheck
# @@Description : Docker container healthcheck — HTTP/TCP/process/file checks
# @@Changelog : Rewrote as a real Docker HEALTHCHECK probe
# @@TODO : Better documentation
# @@Other :
# @@Resource :
# @@Terminal App : no
# @@sudo/root : no
# @@Template : shell/sh
# - - - - - - - - - - - - - - - - - - - - - - - - -
# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329
# - - - - - - - - - - - - - - - - - - - - - - - - -
APPNAME="$(basename -- "$0" 2>/dev/null)"
VERSION="202605051654-git"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Defaults (env vars override built-ins, CLI flags override env vars)
HEALTHCHECK_URL="${HEALTHCHECK_URL:-}"
HEALTHCHECK_HTTP_STATUS="${HEALTHCHECK_HTTP_STATUS:-2,3}"
HEALTHCHECK_HOST="${HEALTHCHECK_HOST:-127.0.0.1}"
HEALTHCHECK_PORT="${HEALTHCHECK_PORT:-}"
HEALTHCHECK_PROCESS="${HEALTHCHECK_PROCESS:-}"
HEALTHCHECK_FILE="${HEALTHCHECK_FILE:-}"
HEALTHCHECK_FILE_MAX_AGE="${HEALTHCHECK_FILE_MAX_AGE:-}"
HEALTHCHECK_TIMEOUT="${HEALTHCHECK_TIMEOUT:-5}"
HEALTHCHECK_VERBOSE="${HEALTHCHECK_VERBOSE:-}"
# - - - - - - - - - - - - - - - - - - - - - - - - -
__cmd_exists() { command -v "$1" >/dev/null 2>&1; }
__log() { [ -n "$HEALTHCHECK_VERBOSE" ] && printf '%s\n' "$*" >&2; return 0; }
__fail() { printf 'UNHEALTHY: %s\n' "$*" >&2; exit 1; }
# - - - - - - - - - - - - - - - - - - - - - - - - -
__usage() {
cat <<EOF
$APPNAME $VERSION — Docker container healthcheck
Usage: $APPNAME [options]
At least one check must be configured (via env var or flag), or the script
exits 1. All configured checks must pass for the container to be healthy.
Options:
--url LIST HTTP(S) URL(s) to GET, comma-separated; ALL must
return an accepted status
(e.g. "http://localhost/health,http://localhost/ready")
--status PREFIXES Accepted status code prefixes, comma-separated
(default: "2,3" — any 2xx or 3xx; e.g. "200,204,301")
--host HOST Host for TCP port check (default: 127.0.0.1)
--port LIST TCP port(s) that must be accepting connections,
comma-separated; ALL must be reachable
(e.g. "80,443,3306")
--process LIST Process name(s) that must be running (matches the
executable name via pgrep). Comma-separated for
multiple — ALL must be present
(e.g. "tini,nginx,postfix,mariadb")
--file LIST File path(s) that must exist, comma-separated; ALL
must exist (and pass --file-max-age, if set)
--file-max-age SECONDS Each file's mtime must be within this many seconds
--timeout SECONDS Network check timeout (default: 5)
-v, --verbose Print check progress to stderr
-h, --help Show this help and exit 0
Environment variables (overridden by flags):
HEALTHCHECK_URL, HEALTHCHECK_HTTP_STATUS, HEALTHCHECK_HOST,
HEALTHCHECK_PORT, HEALTHCHECK_PROCESS, HEALTHCHECK_FILE,
HEALTHCHECK_FILE_MAX_AGE, HEALTHCHECK_TIMEOUT, HEALTHCHECK_VERBOSE
Exit codes:
0 all configured checks passed
1 at least one check failed, or no checks were configured
EOF
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Parse CLI flags (override env vars)
while [ $# -gt 0 ]; do
case "$1" in
--url) HEALTHCHECK_URL="$2"; shift 2 ;;
--url=*) HEALTHCHECK_URL="${1#*=}"; shift ;;
--status) HEALTHCHECK_HTTP_STATUS="$2"; shift 2 ;;
--status=*) HEALTHCHECK_HTTP_STATUS="${1#*=}"; shift ;;
--host) HEALTHCHECK_HOST="$2"; shift 2 ;;
--host=*) HEALTHCHECK_HOST="${1#*=}"; shift ;;
--port) HEALTHCHECK_PORT="$2"; shift 2 ;;
--port=*) HEALTHCHECK_PORT="${1#*=}"; shift ;;
--process) HEALTHCHECK_PROCESS="$2"; shift 2 ;;
--process=*) HEALTHCHECK_PROCESS="${1#*=}"; shift ;;
--file) HEALTHCHECK_FILE="$2"; shift 2 ;;
--file=*) HEALTHCHECK_FILE="${1#*=}"; shift ;;
--file-max-age) HEALTHCHECK_FILE_MAX_AGE="$2"; shift 2 ;;
--file-max-age=*) HEALTHCHECK_FILE_MAX_AGE="${1#*=}"; shift ;;
--timeout) HEALTHCHECK_TIMEOUT="$2"; shift 2 ;;
--timeout=*) HEALTHCHECK_TIMEOUT="${1#*=}"; shift ;;
-v|--verbose) HEALTHCHECK_VERBOSE=1; shift ;;
-h|--help) __usage; exit 0 ;;
--) shift; break ;;
-*) printf 'Unknown option: %s\n' "$1" >&2; __usage >&2; exit 1 ;;
*) printf 'Unexpected argument: %s\n' "$1" >&2; exit 1 ;;
esac
done
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Individual checks — each prints why it failed and exits 1 on failure
__trim() { printf '%s' "$1" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//'; }
__check_one_http() {
url="$1"; accepted="$2"; timeout="$3"
if __cmd_exists curl; then
code="$(curl -ksSL -o /dev/null -w '%{http_code}' --max-time "$timeout" "$url" 2>/dev/null)" \
|| __fail "HTTP request to $url failed (curl error)"
elif __cmd_exists wget; then
code="$(wget -q -S --spider --timeout="$timeout" --tries=1 "$url" 2>&1 \
| awk '/^ HTTP\// {c=$2} END {print c+0}')"
[ "$code" -gt 0 ] 2>/dev/null || __fail "HTTP request to $url failed (wget error)"
else
__fail "HTTP check requires curl or wget"
fi
IFS=','
for prefix in $accepted; do
case "$code" in
"$prefix"*) unset IFS; __log "HTTP ok: $url -> $code"; return 0 ;;
esac
done
unset IFS
__fail "HTTP $url returned $code (expected prefix in: $accepted)"
}
__check_http() {
urls="$1"; accepted="$2"; timeout="$3"
__log "HTTP: urls=$urls (timeout=${timeout}s, accept=${accepted})"
IFS=','
for u in $urls; do
unset IFS
u="$(__trim "$u")"
[ -n "$u" ] || { IFS=','; continue; }
__check_one_http "$u" "$accepted" "$timeout"
IFS=','
done
unset IFS
return 0
}
__check_one_tcp() {
host="$1"; port="$2"; timeout="$3"
if __cmd_exists nc; then
nc -z -w "$timeout" "$host" "$port" >/dev/null 2>&1 && { __log "TCP ok: $host:$port"; return 0; }
fi
if __cmd_exists ncat; then
ncat -z -w "${timeout}s" "$host" "$port" >/dev/null 2>&1 && { __log "TCP ok (ncat): $host:$port"; return 0; }
fi
# Last resort: bash /dev/tcp (only if bash is available; sh-only systems skip)
if __cmd_exists bash; then
bash -c "exec 3<>/dev/tcp/$host/$port" >/dev/null 2>&1 && { __log "TCP ok (bash): $host:$port"; return 0; }
fi
return 1
}
__check_tcp() {
host="$1"; ports="$2"; timeout="$3"
__log "TCP: host=$host ports=$ports (timeout=${timeout}s)"
IFS=','
for p in $ports; do
unset IFS
p="$(__trim "$p")"
[ -n "$p" ] || { IFS=','; continue; }
__check_one_tcp "$host" "$p" "$timeout" || __fail "TCP $host:$p not reachable"
IFS=','
done
unset IFS
return 0
}
__check_one_process() {
pattern="$1"
if __cmd_exists pgrep; then
# Match against process name (not full cmdline) so our own argv doesn't self-match
pgrep -- "$pattern" >/dev/null 2>&1 && return 0
else
# Portable fallback: ps -o comm= prints just the command name
ps -e -o comm= 2>/dev/null | grep -v -e "^grep$" -e "^$APPNAME$" | grep -q -- "$pattern" && return 0
fi
return 1
}
__check_process() {
patterns="$1"
__log "Process: patterns=$patterns"
IFS=','
for p in $patterns; do
unset IFS
p="$(__trim "$p")"
[ -n "$p" ] || { IFS=','; continue; }
__check_one_process "$p" || __fail "Process not running: $p"
__log "Process ok: $p"
IFS=','
done
unset IFS
return 0
}
__check_one_file() {
path="$1"; max_age="$2"
[ -e "$path" ] || __fail "File not found: $path"
if [ -n "$max_age" ]; then
now="$(date +%s)"
mtime="$(stat -c %Y "$path" 2>/dev/null || stat -f %m "$path" 2>/dev/null \
|| perl -e 'print((stat(shift))[9])' "$path" 2>/dev/null)"
[ -n "$mtime" ] || __fail "Cannot determine mtime of $path"
age=$(( now - mtime ))
[ "$age" -le "$max_age" ] || __fail "File $path is stale (age=${age}s, max=${max_age}s)"
fi
__log "File ok: $path"
return 0
}
__check_file() {
paths="$1"; max_age="$2"
__log "File: paths=$paths max_age=${max_age:-none}"
IFS=','
for f in $paths; do
unset IFS
f="$(__trim "$f")"
[ -n "$f" ] || { IFS=','; continue; }
__check_one_file "$f" "$max_age"
IFS=','
done
unset IFS
return 0
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Run checks
ran_any=0
[ -n "$HEALTHCHECK_URL" ] && { __check_http "$HEALTHCHECK_URL" "$HEALTHCHECK_HTTP_STATUS" "$HEALTHCHECK_TIMEOUT"; ran_any=1; }
[ -n "$HEALTHCHECK_PORT" ] && { __check_tcp "$HEALTHCHECK_HOST" "$HEALTHCHECK_PORT" "$HEALTHCHECK_TIMEOUT"; ran_any=1; }
[ -n "$HEALTHCHECK_PROCESS" ] && { __check_process "$HEALTHCHECK_PROCESS"; ran_any=1; }
[ -n "$HEALTHCHECK_FILE" ] && { __check_file "$HEALTHCHECK_FILE" "$HEALTHCHECK_FILE_MAX_AGE"; ran_any=1; }
[ "$ran_any" -eq 1 ] || __fail "no checks configured (set HEALTHCHECK_URL/PORT/PROCESS/FILE or pass --url/--port/--process/--file)"
__log "All checks passed"
exit 0
# - - - - - - - - - - - - - - - - - - - - - - - - -
# ex: ts=2 sw=2 et filetype=sh
+88
View File
@@ -0,0 +1,88 @@
#!/usr/bin/env bash
# shellcheck shell=bash
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202605311104-git
# @@Author : Jason Hempstead
# @@Contact : jason@casjaysdev.pro
# @@License : WTFPL
# @@ReadME : rust-workflow --help
# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments
# @@Created : Sunday, May 31, 2026 11:04 EDT
# @@File : rust-workflow
# @@Description : Default Rust workflow: fmt → clippy → test → build
# @@Changelog : New script
# @@Other :
# @@Resource :
# @@Terminal App : no
# @@sudo/root : no
# @@Template : other/docker-workflow
# - - - - - - - - - - - - - - - - - - - - - - - - -
# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Runs automatically when `docker run casjaysdev/rust` is called with no args.
# Working directory must be a Cargo workspace or crate root (Cargo.toml must exist).
set -euo pipefail
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Resolve the working directory — prefer /app if mounted, then cwd
WORK_DIR="${CARGO_WORKDIR:-${PWD}}"
cd "$WORK_DIR"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Require a Cargo.toml so we fail fast with a clear message instead of
# cryptic cargo errors about missing manifests
if [ ! -f "Cargo.toml" ]; then
echo "Error: no Cargo.toml found in ${WORK_DIR}" >&2
echo "Mount your project with: docker run --rm -v \"\$(pwd)\":/app casjaysdev/rust" >&2
exit 1
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Resolve the crate or workspace name from the manifest
CRATE="$(awk -F'"' '/^\[package\]/{p=1} p && /^name[[:space:]]*=/{print $2; exit}' Cargo.toml)"
[ -z "$CRATE" ] && CRATE="$(basename "$WORK_DIR")"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Optional target override — set CARGO_BUILD_TARGET for cross-compilation or
# static musl builds (e.g. x86_64-unknown-linux-musl)
BUILD_TARGET="${CARGO_BUILD_TARGET:-}"
# Collect extra flags for steps that accept a target
build_flags=(--release)
test_flags=()
if [ -n "$BUILD_TARGET" ]; then
build_flags+=(--target "$BUILD_TARGET")
test_flags+=(--target "$BUILD_TARGET")
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
echo ""
echo "▶ Rust workflow: ${CRATE}"
echo " Working dir: ${WORK_DIR}"
echo " Rust: $(rustc --version)"
echo " Cargo: $(cargo --version)"
[ -n "$BUILD_TARGET" ] && echo " Target: ${BUILD_TARGET}"
echo ""
# - - - - - - - - - - - - - - - - - - - - - - - - -
run_step() {
local label="$1"; shift
echo "── ${label}"
"$@"
echo ""
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# 1. Check formatting — report violations but do not auto-modify source
run_step "cargo fmt --check" cargo fmt --all -- --check
# 2. Lint with clippy across all targets and features; treat warnings as errors
run_step "cargo clippy" cargo clippy --all-targets --all-features -- -D warnings
# 3. Run the full test suite before spending time on a release build
run_step "cargo test --all" cargo test --all "${test_flags[@]}"
# 4. Build the release binary (static if CARGO_BUILD_TARGET is set)
run_step "cargo build --release" cargo build --all "${build_flags[@]}"
echo "✅ Done."
+77
View File
@@ -0,0 +1,77 @@
#!/usr/bin/env sh
# shellcheck shell=sh
# - - - - - - - - - - - - - - - - - - - - - - - - -
##@Version : 202605051306-git
# @@Author : Jason Hempstead
# @@Contact : jason@casjaysdev.pro
# @@License : WTFPL
# @@ReadME : symlink --help
# @@Copyright : Copyright: (c) 2026 Jason Hempstead, Casjays Developments
# @@Created : Tuesday, May 05, 2026 13:06 EDT
# @@File : symlink
# @@Description :
# @@Changelog : New script
# @@TODO : Better documentation
# @@Other :
# @@Resource :
# @@Terminal App : no
# @@sudo/root : no
# @@Template : shell/sh
# - - - - - - - - - - - - - - - - - - - - - - - - -
# shellcheck disable=SC1001,SC1003,SC2001,SC2003,SC2016,SC2031,SC2090,SC2115,SC2120,SC2155,SC2199,SC2229,SC2317,SC2329
# - - - - - - - - - - - - - - - - - - - - - - - - -
APPNAME="$(basename -- "$0" 2>/dev/null)"
# - - - - - - - - - - - - - - - - - - - - - - - - -
# colorization
if [ -n "$NO_COLOR" ]; then
__printf_color() { printf '%b' "$1\n" | tr -d '\t' | sed '/^%b$/d;s,\x1B\[ 0-9;]*[a-zA-Z],,g'; }
else
__printf_color() { { [ -z "$2" ] || DEFAULT_COLOR=$2; } && printf "%b" "$(tput setaf "$DEFAULT_COLOR" 2>/dev/null)" "$1\n" "$(tput sgr0 2>/dev/null)"; }
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
__unlink() { [ -L "$1" ] && rm -f -- "$1" >/dev/null; }
# - - - - - - - - - - - - - - - - - - - - - - - - -
# custom functions
__ln_sf() {
exitCode=0
if [ -d "$1" ] && [ ! -L "$1" ]; then
__printf_color "symlinking contents of $1 into $2/" "4"
__unlink "$2"
mkdir -p "$2"
for f in "$1"/* "$1"/.[!.]* "$1"/..?*; do
[ -e "$f" ] || [ -L "$f" ] || continue
base=$(basename -- "$f")
__ln_sf "$f" "$2/$base" || exitCode=$?
done
else
__printf_color "symlinking $2 to $1" "4"
__unlink "$2"
ln -sf "$1" "$2"
exitCode=$?
fi
return $exitCode
}
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Define variables
DEFAULT_COLOR="254"
SYMLINK_EXIT_STATUS=0
# - - - - - - - - - - - - - - - - - - - - - - - - -
# Main application
if [ $# -ne 2 ]; then
__printf_color "USAGE: $APPNAME from to" "2" >&2
SYMLINK_EXIT_STATUS=1
elif [ ! -e "$1" ]; then
__printf_color "$1 does not exist" >&2
SYMLINK_EXIT_STATUS=2
else
__ln_sf "$1" "$2" >/dev/null
SYMLINK_EXIT_STATUS=$?
fi
# - - - - - - - - - - - - - - - - - - - - - - - - -
# End application
# - - - - - - - - - - - - - - - - - - - - - - - - -
# lets exit with code
# - - - - - - - - - - - - - - - - - - - - - - - - -
exit $SYMLINK_EXIT_STATUS
# - - - - - - - - - - - - - - - - - - - - - - - - -
# ex: ts=2 sw=2 et filetype=sh