mirror of
https://github.com/dockersrc/rust
synced 2026-06-24 14:01:04 -04:00
✨ 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:
@@ -8,9 +8,8 @@
|
||||
# @@Copyright : Copyright 2026 CasjaysDev
|
||||
# @@Created : Sun May 31 11:04:50 AM EDT 2026
|
||||
# @@File : 05-custom.sh
|
||||
# @@Description : script to run custom
|
||||
# @@Description : Install latest stable Rust toolchain with static-build tooling
|
||||
# @@Changelog : newScript
|
||||
# @@TODO : Refactor code
|
||||
# @@Other : N/A
|
||||
# @@Resource : N/A
|
||||
# @@Terminal App : yes
|
||||
@@ -32,6 +31,245 @@ exitCode=0
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# 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
|
||||
exitCode=$?
|
||||
@@ -40,4 +278,3 @@ exit $exitCode
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
# ex: ts=2 sw=2 et filetype=sh
|
||||
# - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
|
||||
Executable
+78
@@ -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
|
||||
@@ -506,9 +506,11 @@ if [ "$START_SERVICES" = "yes" ] || [ -z "$1" ]; then
|
||||
echo "$$" >"$ENTRYPOINT_PID_FILE"
|
||||
__start_init_scripts "/usr/local/etc/docker/init.d"
|
||||
CONTAINER_INIT="${CONTAINER_INIT:-no}"
|
||||
# Services started successfully - enter monitoring mode
|
||||
__no_exit
|
||||
exit $?
|
||||
# Only block in monitoring mode when no user command was given
|
||||
if [ $# -eq 0 ]; then
|
||||
__no_exit
|
||||
exit $?
|
||||
fi
|
||||
fi
|
||||
START_SERVICES="no"
|
||||
fi
|
||||
@@ -674,11 +676,8 @@ start)
|
||||
# Execute primary command
|
||||
*)
|
||||
if [ $# -eq 0 ]; then
|
||||
if [ ! -f "$ENTRYPOINT_PID_FILE" ]; then
|
||||
echo "$$" >"$ENTRYPOINT_PID_FILE"
|
||||
[ "$START_SERVICES" = "no" ] && [ "$CONTAINER_INIT" = "yes" ] || __start_init_scripts "/usr/local/etc/docker/init.d"
|
||||
fi
|
||||
__no_exit
|
||||
# No args: run the default Rust workflow (fmt → clippy → test → build)
|
||||
__exec_command rust-workflow
|
||||
else
|
||||
__exec_command "$@"
|
||||
fi
|
||||
|
||||
Executable
+249
@@ -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
|
||||
Executable
+88
@@ -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."
|
||||
Executable
+77
@@ -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
|
||||
Reference in New Issue
Block a user