From e9c9a2d1f26ec9d266db12b90f2d57354839ae3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=AE=89=E6=AD=A3=E8=B6=85?= Date: Tue, 19 Aug 2025 22:58:54 +0800 Subject: [PATCH] fix: simplify Docker entrypoint following efficient user switching pattern (#421) * fix: simplify Docker entrypoint following efficient user switching pattern - Remove ALL file permission modifications (no chown at all) - Use chroot --userspec or gosu to switch user context - Extremely simple and fast implementation - Zero filesystem modifications for permissions Fixes #388 * Update entrypoint.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update entrypoint.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update entrypoint.sh Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * wip * wip * wip --------- Co-authored-by: Cursor Agent Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- Dockerfile | 130 +++++++++++++-------------- Dockerfile.source | 223 ++++++++++++++++++++++++++-------------------- Makefile | 6 +- entrypoint.sh | 165 +++++++++++++++------------------- 4 files changed, 260 insertions(+), 264 deletions(-) mode change 100644 => 100755 entrypoint.sh diff --git a/Dockerfile b/Dockerfile index b04e7859..6b803078 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,58 +1,56 @@ -# Multi-stage build for RustFS production image - -# Build stage: Download and extract RustFS binary +# ------------------- +# Build stage +# ------------------- FROM alpine:3.22 AS build -# Build arguments for platform and release ARG TARGETARCH ARG RELEASE=latest -# Install minimal dependencies for downloading and extracting RUN apk add --no-cache ca-certificates curl unzip - -# Create build directory WORKDIR /build -# Set architecture-specific variables -RUN if [ "$TARGETARCH" = "amd64" ]; then \ - echo "x86_64-musl" > /tmp/arch; \ - elif [ "$TARGETARCH" = "arm64" ]; then \ - echo "aarch64-musl" > /tmp/arch; \ +# Download and extract release package matching current TARGETARCH +# - If RELEASE=latest: take first tag_name from /releases (may include pre-releases) +# - Otherwise use specified tag (e.g. v0.1.2) +RUN set -eux; \ + case "$TARGETARCH" in \ + amd64) ARCH_SUBSTR="x86_64-musl" ;; \ + arm64) ARCH_SUBSTR="aarch64-musl" ;; \ + *) echo "Unsupported TARGETARCH=$TARGETARCH" >&2; exit 1 ;; \ + esac; \ + if [ "$RELEASE" = "latest" ]; then \ + TAG="$(curl -fsSL https://api.github.com/repos/rustfs/rustfs/releases \ + | grep -o '"tag_name": "[^"]*"' | cut -d'"' -f4 | head -n 1)"; \ else \ - echo "unsupported" > /tmp/arch; \ - fi -RUN ARCH=$(cat /tmp/arch) && \ - if [ "$ARCH" = "unsupported" ]; then \ - echo "Unsupported architecture: $TARGETARCH" && exit 1; \ - fi && \ - if [ "${RELEASE}" = "latest" ]; then \ - # For latest, download from GitHub releases using the -latest suffix - PACKAGE_NAME="rustfs-linux-${ARCH}-latest.zip"; \ - # Use GitHub API to get the latest release URL - LATEST_RELEASE_URL=$(curl -s https://api.github.com/repos/rustfs/rustfs/releases/latest | grep -o '"browser_download_url": "[^"]*'"${PACKAGE_NAME}"'"' | cut -d'"' -f4 | head -1); \ - if [ -z "$LATEST_RELEASE_URL" ]; then \ - echo "Failed to find latest release for ${PACKAGE_NAME}" >&2; \ - exit 1; \ - fi; \ - DOWNLOAD_URL="$LATEST_RELEASE_URL"; \ - else \ - # For specific versions, construct the GitHub release URL directly - # RELEASE is the GitHub release tag (e.g., "1.0.0-alpha.42") - # VERSION is the version in filename (e.g., "v1.0.0-alpha.42") - VERSION="v${RELEASE}"; \ - PACKAGE_NAME="rustfs-linux-${ARCH}-${VERSION}.zip"; \ - DOWNLOAD_URL="https://github.com/rustfs/rustfs/releases/download/${RELEASE}/${PACKAGE_NAME}"; \ - fi && \ - echo "Downloading ${PACKAGE_NAME} from ${DOWNLOAD_URL}" >&2 && \ - curl -f -L "${DOWNLOAD_URL}" -o rustfs.zip && \ - unzip rustfs.zip -d /build && \ - chmod +x /build/rustfs && \ - rm rustfs.zip || { echo "Failed to download or extract ${PACKAGE_NAME}" >&2; exit 1; } + TAG="$RELEASE"; \ + fi; \ + echo "Using tag: $TAG (arch pattern: $ARCH_SUBSTR)"; \ + # Find download URL in assets list for this tag that contains arch substring and ends with .zip + URL="$(curl -fsSL "https://api.github.com/repos/rustfs/rustfs/releases/tags/$TAG" \ + | grep -o "\"browser_download_url\": \"[^\"]*${ARCH_SUBSTR}[^\"]*\\.zip\"" \ + | cut -d'"' -f4 | head -n 1)"; \ + if [ -z "$URL" ]; then echo "Failed to locate release asset for $ARCH_SUBSTR at tag $TAG" >&2; exit 1; fi; \ + echo "Downloading: $URL"; \ + curl -fL "$URL" -o rustfs.zip; \ + unzip -q rustfs.zip -d /build; \ + # If binary is not in root directory, try to locate and move from zip to /build/rustfs + if [ ! -x /build/rustfs ]; then \ + BIN_PATH="$(unzip -Z -1 rustfs.zip | grep -E '(^|/)rustfs$' | head -n 1 || true)"; \ + if [ -n "$BIN_PATH" ]; then \ + mkdir -p /build/.tmp && unzip -q rustfs.zip "$BIN_PATH" -d /build/.tmp && \ + mv "/build/.tmp/$BIN_PATH" /build/rustfs; \ + fi; \ + fi; \ + [ -x /build/rustfs ] || { echo "rustfs binary not found in asset" >&2; exit 1; }; \ + chmod +x /build/rustfs; \ + rm -rf rustfs.zip /build/.tmp || true -# Runtime stage: Configure runtime environment -FROM alpine:3.22.1 -# Build arguments and labels +# ------------------- +# Runtime stage +# ------------------- +FROM alpine:3.22 + ARG RELEASE=latest ARG BUILD_DATE ARG VCS_REF @@ -60,7 +58,7 @@ ARG VCS_REF LABEL name="RustFS" \ vendor="RustFS Team" \ maintainer="RustFS Team " \ - version="${RELEASE}" \ + version="v${RELEASE#v}" \ release="${RELEASE}" \ build-date="${BUILD_DATE}" \ vcs-ref="${VCS_REF}" \ @@ -69,43 +67,37 @@ LABEL name="RustFS" \ url="https://rustfs.com" \ license="Apache-2.0" -# Install runtime dependencies -RUN echo "https://dl-cdn.alpinelinux.org/alpine/v3.20/community" >> /etc/apk/repositories && \ - apk update && \ - apk add --no-cache ca-certificates bash gosu coreutils shadow && \ +# Install only runtime requirements: certificates and coreutils (provides chroot --userspec) +RUN apk add --no-cache ca-certificates coreutils && \ addgroup -g 1000 rustfs && \ - adduser -u 1000 -G rustfs -s /bin/bash -D rustfs + adduser -u 1000 -G rustfs -s /sbin/nologin -D rustfs -# Copy CA certificates and RustFS binary from build stage +# Copy binary and entry script (ensure fixed entrypoint.sh exists in repository) COPY --from=build /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ COPY --from=build /build/rustfs /usr/bin/rustfs - -# Copy entry point script COPY entrypoint.sh /entrypoint.sh -# Set permissions RUN chmod +x /usr/bin/rustfs /entrypoint.sh && \ mkdir -p /data /logs && \ chown rustfs:rustfs /data /logs && \ - chmod 700 /data /logs + chmod 0750 /data /logs -# Environment variables (credentials should be set via environment or secrets) -ENV RUSTFS_ADDRESS=:9000 \ - RUSTFS_ACCESS_KEY=rustfsadmin \ - RUSTFS_SECRET_KEY=rustfsadmin \ - RUSTFS_CONSOLE_ENABLE=true \ - RUSTFS_VOLUMES=/data \ - RUST_LOG=warn \ - RUSTFS_OBS_LOG_DIRECTORY=/logs \ - RUSTFS_SINKS_FILE_PATH=/logs +# Default environment (can be overridden in docker run/compose) +ENV RUSTFS_ADDRESS=":9000" \ + RUSTFS_ACCESS_KEY="rustfsadmin" \ + RUSTFS_SECRET_KEY="rustfsadmin" \ + RUSTFS_CONSOLE_ENABLE="true" \ + RUSTFS_VOLUMES="/data" \ + RUST_LOG="warn" \ + RUSTFS_OBS_LOG_DIRECTORY="/logs" \ + RUSTFS_SINKS_FILE_PATH="/logs" \ + RUSTFS_USERNAME="rustfs" \ + RUSTFS_GROUPNAME="rustfs" \ + RUSTFS_UID="1000" \ + RUSTFS_GID="1000" -# Expose port EXPOSE 9000 - -# Volumes for data and logs VOLUME ["/data", "/logs"] -# Set entry point ENTRYPOINT ["/entrypoint.sh"] CMD ["/usr/bin/rustfs"] - diff --git a/Dockerfile.source b/Dockerfile.source index ede0177e..07f26c57 100644 --- a/Dockerfile.source +++ b/Dockerfile.source @@ -1,80 +1,88 @@ +# syntax=docker/dockerfile:1.6 # Multi-stage Dockerfile for RustFS - LOCAL DEVELOPMENT ONLY # -# ⚠️ IMPORTANT: This Dockerfile is for local development and testing only. -# ⚠️ It builds RustFS from source code and is NOT used in CI/CD pipelines. -# ⚠️ CI/CD pipeline uses pre-built binaries from Dockerfile instead. +# IMPORTANT: This Dockerfile builds RustFS from source for local development and testing. +# CI/CD uses the production Dockerfile with prebuilt binaries instead. # -# Usage for local development: +# Example: # docker build -f Dockerfile.source -t rustfs:dev-local . # docker run --rm -p 9000:9000 rustfs:dev-local # -# Supports cross-compilation for amd64 and arm64 architectures +# Supports cross-compilation for amd64 and arm64 via TARGETPLATFORM. + ARG TARGETPLATFORM ARG BUILDPLATFORM +# ----------------------------- # Build stage -FROM --platform=$BUILDPLATFORM rust:1.88-bookworm AS builder +# ----------------------------- +FROM rust:1.88-bookworm AS builder -# Re-declare build arguments after FROM (required for multi-stage builds) +# Re-declare args after FROM ARG TARGETPLATFORM ARG BUILDPLATFORM -# Debug: Print platform information -RUN echo "🐳 Build Info: BUILDPLATFORM=$BUILDPLATFORM, TARGETPLATFORM=$TARGETPLATFORM" +# Debug: print platforms +RUN echo "Build info -> BUILDPLATFORM=${BUILDPLATFORM}, TARGETPLATFORM=${TARGETPLATFORM}" -# Install required build dependencies -RUN apt-get update && apt-get install -y \ - wget \ - git \ +# Install build toolchain and headers +# Use distro packages for protoc/flatc to avoid host-arch mismatch +RUN set -eux; \ + export DEBIAN_FRONTEND=noninteractive; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ + build-essential \ + ca-certificates \ curl \ - unzip \ - gcc \ + git \ pkg-config \ libssl-dev \ lld \ - && rm -rf /var/lib/apt/lists/* + protobuf-compiler \ + flatbuffers-compiler; \ + rm -rf /var/lib/apt/lists/* -# Note: sccache removed for simpler builds - -# Install cross-compilation tools for ARM64 -RUN if [ "$TARGETPLATFORM" = "linux/arm64" ]; then \ - apt-get update && \ - apt-get install -y gcc-aarch64-linux-gnu && \ - rm -rf /var/lib/apt/lists/*; \ +# Optional: cross toolchain for aarch64 (only when targeting linux/arm64) +RUN set -eux; \ + if [ "${TARGETPLATFORM:-linux/amd64}" = "linux/arm64" ]; then \ + export DEBIAN_FRONTEND=noninteractive; \ + apt-get update; \ + apt-get install -y --no-install-recommends gcc-aarch64-linux-gnu; \ + rm -rf /var/lib/apt/lists/*; \ fi -# Install protoc -RUN wget https://github.com/protocolbuffers/protobuf/releases/download/v31.1/protoc-31.1-linux-x86_64.zip \ - && unzip protoc-31.1-linux-x86_64.zip -d protoc3 \ - && mv protoc3/bin/* /usr/local/bin/ && chmod +x /usr/local/bin/protoc \ - && mv protoc3/include/* /usr/local/include/ && rm -rf protoc-31.1-linux-x86_64.zip protoc3 - -# Install flatc -RUN wget https://github.com/google/flatbuffers/releases/download/v25.2.10/Linux.flatc.binary.g++-13.zip \ - && unzip Linux.flatc.binary.g++-13.zip \ - && mv flatc /usr/local/bin/ && chmod +x /usr/local/bin/flatc && rm -rf Linux.flatc.binary.g++-13.zip - -# Set up Rust targets based on platform -RUN set -e && \ - PLATFORM="${TARGETPLATFORM:-linux/amd64}" && \ - echo "🎯 Setting up Rust target for platform: $PLATFORM" && \ - case "$PLATFORM" in \ - "linux/amd64") rustup target add x86_64-unknown-linux-gnu ;; \ - "linux/arm64") rustup target add aarch64-unknown-linux-gnu ;; \ - *) echo "❌ Unsupported platform: $PLATFORM" && exit 1 ;; \ +# Add Rust targets based on TARGETPLATFORM +RUN set -eux; \ + case "${TARGETPLATFORM:-linux/amd64}" in \ + linux/amd64) rustup target add x86_64-unknown-linux-gnu ;; \ + linux/arm64) rustup target add aarch64-unknown-linux-gnu ;; \ + *) echo "Unsupported TARGETPLATFORM=${TARGETPLATFORM}" >&2; exit 1 ;; \ esac -# Set up environment for cross-compilation +# Cross-compilation environment (used only when targeting aarch64) ENV CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc ENV CC_aarch64_unknown_linux_gnu=aarch64-linux-gnu-gcc ENV CXX_aarch64_unknown_linux_gnu=aarch64-linux-gnu-g++ WORKDIR /usr/src/rustfs -# Copy all source code +# Layered copy to maximize caching: +# 1) top-level manifests +COPY Cargo.toml Cargo.lock ./ +# 2) workspace member manifests (adjust if workspace layout changes) +COPY rustfs/Cargo.toml rustfs/Cargo.toml +COPY crates/*/Cargo.toml crates/ +COPY cli/rustfs-gui/Cargo.toml cli/rustfs-gui/Cargo.toml + +# Pre-fetch dependencies for better caching +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + cargo fetch --locked || true + +# 3) copy full sources (this is the main cache invalidation point) COPY . . -# Configure cargo for optimized builds +# Cargo build configuration for lean release artifacts ENV CARGO_NET_GIT_FETCH_WITH_CLI=true \ CARGO_REGISTRIES_CRATES_IO_PROTOCOL=sparse \ CARGO_INCREMENTAL=0 \ @@ -82,75 +90,92 @@ ENV CARGO_NET_GIT_FETCH_WITH_CLI=true \ CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO=off \ CARGO_PROFILE_RELEASE_STRIP=symbols -# Generate protobuf code -RUN cargo run --bin gproto +# Generate protobuf/flatbuffers code (uses protoc/flatc from distro) +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=/usr/src/rustfs/target \ + cargo run --bin gproto -# Build the actual application with optimizations -RUN case "$TARGETPLATFORM" in \ - "linux/amd64") \ - echo "🔨 Building for amd64..." && \ - rustup target add x86_64-unknown-linux-gnu && \ - cargo build --release --target x86_64-unknown-linux-gnu --bin rustfs -j $(nproc) && \ - cp target/x86_64-unknown-linux-gnu/release/rustfs /usr/local/bin/rustfs \ - ;; \ - "linux/arm64") \ - echo "🔨 Building for arm64..." && \ - rustup target add aarch64-unknown-linux-gnu && \ - cargo build --release --target aarch64-unknown-linux-gnu --bin rustfs -j $(nproc) && \ - cp target/aarch64-unknown-linux-gnu/release/rustfs /usr/local/bin/rustfs \ - ;; \ - *) \ - echo "❌ Unsupported platform: $TARGETPLATFORM" && exit 1 \ - ;; \ +# Build RustFS (target depends on TARGETPLATFORM) +RUN --mount=type=cache,target=/usr/local/cargo/registry \ + --mount=type=cache,target=/usr/local/cargo/git \ + --mount=type=cache,target=/usr/src/rustfs/target \ + set -eux; \ + case "${TARGETPLATFORM:-linux/amd64}" in \ + linux/amd64) \ + echo "Building for x86_64-unknown-linux-gnu"; \ + cargo build --release --locked --target x86_64-unknown-linux-gnu --bin rustfs -j "$(nproc)"; \ + install -m 0755 target/x86_64-unknown-linux-gnu/release/rustfs /usr/local/bin/rustfs \ + ;; \ + linux/arm64) \ + echo "Building for aarch64-unknown-linux-gnu"; \ + cargo build --release --locked --target aarch64-unknown-linux-gnu --bin rustfs -j "$(nproc)"; \ + install -m 0755 target/aarch64-unknown-linux-gnu/release/rustfs /usr/local/bin/rustfs \ + ;; \ + *) \ + echo "Unsupported TARGETPLATFORM=${TARGETPLATFORM}" >&2; exit 1 \ + ;; \ esac -# Runtime stage - Ubuntu minimal for better compatibility +# ----------------------------- +# Runtime stage (Ubuntu minimal) +# ----------------------------- FROM ubuntu:22.04 -# Install runtime dependencies -RUN apt-get update && apt-get install -y \ +ARG BUILD_DATE +ARG VCS_REF + +LABEL name="RustFS (dev-local)" \ + maintainer="RustFS Team" \ + build-date="${BUILD_DATE}" \ + vcs-ref="${VCS_REF}" \ + description="RustFS - local development image built from source (NOT for production)." + +# Minimal runtime deps: certificates + tzdata + coreutils (for chroot --userspec) +RUN set -eux; \ + export DEBIAN_FRONTEND=noninteractive; \ + apt-get update; \ + apt-get install -y --no-install-recommends \ ca-certificates \ tzdata \ - wget \ - coreutils \ - passwd \ - && rm -rf /var/lib/apt/lists/* + coreutils; \ + rm -rf /var/lib/apt/lists/* -# Create rustfs user and group -RUN groupadd -g 1000 rustfs && \ - useradd -d /app -g rustfs -u 1000 -s /bin/bash rustfs +# Create a conventional runtime user/group (final switch happens in entrypoint via chroot --userspec) +RUN set -eux; \ + groupadd -g 1000 rustfs; \ + useradd -u 1000 -g rustfs -M -s /usr/sbin/nologin rustfs WORKDIR /app -# Create data directories -RUN mkdir -p /data/rustfs{0,1,2,3} && \ - chown -R rustfs:rustfs /data /app +# Prepare data/log directories with sane defaults +RUN set -eux; \ + mkdir -p /data /logs; \ + chown -R rustfs:rustfs /data /logs /app; \ + chmod 0750 /data /logs -# Copy binary from builder stage -COPY --from=builder /usr/local/bin/rustfs /app/rustfs -RUN chmod +x /app/rustfs && chown rustfs:rustfs /app/rustfs - -# Copy entrypoint script +# Copy the freshly built binary and the entrypoint +COPY --from=builder /usr/local/bin/rustfs /usr/bin/rustfs COPY entrypoint.sh /entrypoint.sh -RUN chmod +x /entrypoint.sh +RUN chmod +x /usr/bin/rustfs /entrypoint.sh -# Switch to non-root user -USER rustfs +# Default environment (override in docker run/compose as needed) +ENV RUSTFS_ADDRESS=":9000" \ + RUSTFS_ACCESS_KEY="rustfsadmin" \ + RUSTFS_SECRET_KEY="rustfsadmin" \ + RUSTFS_CONSOLE_ENABLE="true" \ + RUSTFS_VOLUMES="/data" \ + RUST_LOG="warn" \ + RUSTFS_OBS_LOG_DIRECTORY="/logs" \ + RUSTFS_SINKS_FILE_PATH="/logs" \ + RUSTFS_USERNAME="rustfs" \ + RUSTFS_GROUPNAME="rustfs" \ + RUSTFS_UID="1000" \ + RUSTFS_GID="1000" -# Expose ports EXPOSE 9000 +VOLUME ["/data", "/logs"] -# Environment variables -ENV RUSTFS_ACCESS_KEY=rustfsadmin \ - RUSTFS_SECRET_KEY=rustfsadmin \ - RUSTFS_ADDRESS=":9000" \ - RUSTFS_CONSOLE_ENABLE=true \ - RUSTFS_VOLUMES=/data \ - RUST_LOG=warn - -# Volume for data -VOLUME ["/data"] - -# Set entrypoint and default command +# Keep root here; entrypoint will drop privileges using chroot --userspec ENTRYPOINT ["/entrypoint.sh"] -CMD ["/app/rustfs"] +CMD ["/usr/bin/rustfs"] diff --git a/Makefile b/Makefile index 5fbf2865..363cd557 100644 --- a/Makefile +++ b/Makefile @@ -23,7 +23,7 @@ fmt-check: .PHONY: clippy clippy: @echo "🔍 Running clippy checks..." - cargo clippy --fix --allow-dirty + cargo clippy --fix --allow-dirty cargo clippy --all-targets --all-features -- -D warnings .PHONY: check @@ -210,7 +210,9 @@ docker-build-production: docker-build-source: @echo "🏗️ Building single-architecture source Docker image..." @echo "💡 Consider using 'make docker-dev-local' for multi-arch support" - $(DOCKER_CLI) build -f $(DOCKERFILE_SOURCE) -t rustfs:source . + DOCKER_BUILDKIT=1 $(DOCKER_CLI) build \ + --build-arg BUILDKIT_INLINE_CACHE=1 \ + -f $(DOCKERFILE_SOURCE) -t rustfs:source . # ======================================================================================== # Development Environment diff --git a/entrypoint.sh b/entrypoint.sh old mode 100644 new mode 100755 index 25bcf6bc..e7351870 --- a/entrypoint.sh +++ b/entrypoint.sh @@ -1,104 +1,81 @@ -#!/bin/bash +#!/bin/sh set -e -APP_USER=rustfs -APP_GROUP=rustfs -APP_UID=${PUID:-1000} -APP_GID=${PGID:-1000} - -# Parse RUSTFS_VOLUMES into array (support space, comma, tab as separator) -VOLUME_RAW="${RUSTFS_VOLUMES:-/data}" -# Replace comma and tab with space, then split -VOLUME_RAW=$(echo "$VOLUME_RAW" | tr ',\t' ' ') -read -ra ALL_VOLUMES <<< "$VOLUME_RAW" - -# Only keep local volumes (start with /, not http/https) -LOCAL_VOLUMES=() -for vol in "${ALL_VOLUMES[@]}"; do - if [[ "$vol" =~ ^/ ]] && [[ ! "$vol" =~ ^https?:// ]]; then - # Not a URL (http/https), just a local path - LOCAL_VOLUMES+=("$vol") - fi - # If it's a URL (http/https), skip - # If it's an empty string, skip - # If it's a local path, keep - # (We don't support other protocols here) -done - -# Always ensure /logs is included for permission fix -include_logs=1 -for vol in "${LOCAL_VOLUMES[@]}"; do - if [ "$vol" = "/logs" ]; then - include_logs=0 - break - fi -done -if [ $include_logs -eq 1 ]; then - LOCAL_VOLUMES+=("/logs") +# 1) Normalize command: +# - No arguments: default to execute rustfs +# - First argument starts with '-': treat as rustfs arguments, auto-prefix rustfs +# - First argument is 'rustfs': replace with absolute path to avoid PATH interference +if [ $# -eq 0 ] || [ "${1#-}" != "$1" ]; then + set -- /usr/bin/rustfs "$@" +elif [ "$1" = "rustfs" ]; then + shift + set -- /usr/bin/rustfs "$@" fi -# Try to update rustfs UID/GID if needed (requires root and shadow tools) -update_user_group_ids() { - local uid="$1" - local gid="$2" - local user="$3" - local group="$4" - local updated=0 - if [ "$(id -u "$user")" != "$uid" ]; then - if command -v usermod >/dev/null 2>&1; then - echo "🔧 Updating UID of $user to $uid" - usermod -u "$uid" "$user" - updated=1 +# 2) Parse and create local mount directories (ignore http/https), ensure /logs is included +VOLUME_RAW="${RUSTFS_VOLUMES:-/data}" +# Convert comma/tab to space +VOLUME_LIST=$(echo "$VOLUME_RAW" | tr ',\t' ' ') +LOCAL_VOLUMES="" +for vol in $VOLUME_LIST; do + case "$vol" in + /*) + case "$vol" in + http://*|https://*) : ;; + *) LOCAL_VOLUMES="$LOCAL_VOLUMES $vol" ;; + esac + ;; + *) + : # skip non-local paths + ;; + esac +done +# Ensure /logs is included +case " $LOCAL_VOLUMES " in + *" /logs "*) : ;; + *) LOCAL_VOLUMES="$LOCAL_VOLUMES /logs" ;; +esac + +echo "Initializing mount directories:$LOCAL_VOLUMES" +for vol in $LOCAL_VOLUMES; do + if [ ! -d "$vol" ]; then + echo " mkdir -p $vol" + mkdir -p "$vol" + # If target user is specified, try to set directory owner to that user (non-recursive to avoid large disk overhead) + if [ -n "$RUSTFS_UID" ] && [ -n "$RUSTFS_GID" ]; then + chown "$RUSTFS_UID:$RUSTFS_GID" "$vol" 2>/dev/null || true + elif [ -n "$RUSTFS_USERNAME" ] && [ -n "$RUSTFS_GROUPNAME" ]; then + chown "$RUSTFS_USERNAME:$RUSTFS_GROUPNAME" "$vol" 2>/dev/null || true fi fi - if [ "$(id -g "$group")" != "$gid" ]; then - if command -v groupmod >/dev/null 2>&1; then - echo "🔧 Updating GID of $group to $gid" - groupmod -g "$gid" "$group" - updated=1 +done + +# 3) Default credentials warning +if [ "${RUSTFS_ACCESS_KEY}" = "rustfsadmin" ] || [ "${RUSTFS_SECRET_KEY}" = "rustfsadmin" ]; then + echo "!!!WARNING: Using default RUSTFS_ACCESS_KEY or RUSTFS_SECRET_KEY. Override them in production!" +fi + +# 4) Start with specified user +docker_switch_user() { + if [ -n "${RUSTFS_USERNAME}" ] && [ -n "${RUSTFS_GROUPNAME}" ]; then + if [ -n "${RUSTFS_UID}" ] && [ -n "${RUSTFS_GID}" ]; then + # Execute with numeric UID:GID directly (doesn't depend on user existing in system) + exec chroot --userspec="${RUSTFS_UID}:${RUSTFS_GID}" / "$@" + else + # When only names are provided, create minimal passwd/group entries with 1000:1000; deduplicate before writing + if ! grep -q "^${RUSTFS_USERNAME}:" /etc/passwd 2>/dev/null; then + echo "${RUSTFS_USERNAME}:x:1000:1000:${RUSTFS_USERNAME}:/nonexistent:/sbin/nologin" >> /etc/passwd + fi + if ! grep -q "^${RUSTFS_GROUPNAME}:" /etc/group 2>/dev/null; then + echo "${RUSTFS_GROUPNAME}:x:1000:" >> /etc/group + fi + exec chroot --userspec="${RUSTFS_USERNAME}:${RUSTFS_GROUPNAME}" / "$@" fi + else + # If no user is specified, keep as root (container has minimal privilege practices that can be configured separately) + exec "$@" fi - return $updated } -echo "📦 Initializing mount directories: ${LOCAL_VOLUMES[*]}" - -for vol in "${LOCAL_VOLUMES[@]}"; do - if [ ! -d "$vol" ]; then - echo "📁 Creating directory: $vol" - mkdir -p "$vol" - fi - - # Alpine busybox stat does not support -c, coreutils is required - dir_uid=$(stat -c '%u' "$vol") - dir_gid=$(stat -c '%g' "$vol") - - if [ "$dir_uid" != "$APP_UID" ] || [ "$dir_gid" != "$APP_GID" ]; then - if [[ "$SKIP_CHOWN" != "true" ]]; then - # Prefer to update rustfs user/group UID/GID - update_user_group_ids "$dir_uid" "$dir_gid" "$APP_USER" "$APP_GROUP" || \ - { - echo "🔧 Fixing ownership for: $vol → $APP_USER:$APP_GROUP" - if [[ -n "$CHOWN_RECURSION_DEPTH" ]]; then - echo "🔧 Applying ownership fix with recursion depth: $CHOWN_RECURSION_DEPTH" - find "$vol" -mindepth 0 -maxdepth "$CHOWN_RECURSION_DEPTH" -exec chown "$APP_USER:$APP_GROUP" {} \; - else - echo "🔧 Applying ownership fix recursively (full depth)" - chown -R "$APP_USER:$APP_GROUP" "$vol" - fi - } - else - echo "⚠️ SKIP_CHOWN is enabled. Skipping ownership fix for: $vol" - fi - fi - chmod 700 "$vol" -done - -# Warn if default credentials are used -if [[ "$RUSTFS_ACCESS_KEY" == "rustfsadmin" || "$RUSTFS_SECRET_KEY" == "rustfsadmin" ]]; then - echo "⚠️ WARNING: Using default RUSTFS_ACCESS_KEY or RUSTFS_SECRET_KEY" - echo "⚠️ It is strongly recommended to override these values in production!" -fi - -echo "🚀 Starting application: $*" -exec gosu "$APP_USER" "$@" +echo "Starting: $*" +docker_switch_user "$@"