diff --git a/Dockerfile b/Dockerfile index e68a3f8..d156bb8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -224,8 +224,20 @@ RUN wget -qO - http://repo.cubecoders.com/archive.key | gpg --dearmor > /etc/apt /var/lib/apt/lists/* \ /var/tmp/* +# Install Docker CLI (for Docker-out-of-Docker scenarios) +RUN install -m 0755 -d /etc/apt/keyrings && \ + curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg && \ + chmod a+r /etc/apt/keyrings/docker.gpg && \ + echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian $(. /etc/os-release && echo \"$VERSION_CODENAME\") stable" > /etc/apt/sources.list.d/docker.list && \ + apt-get update && \ + apt-get install -y docker-ce-cli docker-buildx-plugin docker-compose-plugin && \ + rm -rf /var/lib/apt/lists/* + # Set up environment COPY entrypoint /opt/entrypoint -RUN chmod -R +x /opt/entrypoint +RUN chmod -R +x /opt/entrypoint && \ + find /opt/entrypoint -type f -name "*.sh" -exec sed -i 's/\r$//' {} \; && \ + install -m 0755 /opt/entrypoint/docker-wrapper.sh /usr/local/bin/docker && \ + sed -i 's/\r$//' /usr/local/bin/docker ENTRYPOINT ["/opt/entrypoint/main.sh"] diff --git a/entrypoint/docker-wrapper.sh b/entrypoint/docker-wrapper.sh new file mode 100644 index 0000000..83d0d3a --- /dev/null +++ b/entrypoint/docker-wrapper.sh @@ -0,0 +1,166 @@ +#!/bin/bash +set -euo pipefail + +# Docker wrapper to adjust volume mounts for AMP container +REAL_DOCKER="/usr/bin/docker" +CONTAINER_PREFIX="/home/amp" + +# Normalize a path by removing trailing slashes +normalize_path() { + local value="$1" + if [ -z "$value" ]; then + echo "" + return + fi + while [ "${#value}" -gt 1 ] && [ "${value%/}" != "$value" ]; do + value="${value%/}" + done + echo "$value" +} + +# Decode escaped characters in mount paths +decode_mount_path() { + local path="$1" + path="${path//\\040/ }" + path="${path//\\011/$'\t'}" + path="${path//\\012/$'\n'}" + path="${path//\\134/\\}" + echo "$path" +} + +# Detect host mount prefix for the container prefix +detect_host_prefix() { + local mount_line + mount_line=$(awk -v mp="$CONTAINER_PREFIX" '$5==mp {print $4; exit}' /proc/self/mountinfo 2>/dev/null || true) + if [ -z "$mount_line" ] || [ "$mount_line" = "/" ]; then + echo "" + return + fi + decode_mount_path "$mount_line" +} + +# Determine HOST_PREFIX +HOST_PREFIX="${AMP_HOST_HOME:-}" +if [ -z "$HOST_PREFIX" ]; then + HOST_PREFIX="$(detect_host_prefix)" +fi +HOST_PREFIX="$(normalize_path "$HOST_PREFIX")" +CONTAINER_PREFIX="$(normalize_path "$CONTAINER_PREFIX")" + +# If no host prefix detected or same as container, run docker as-is +if [ -z "$HOST_PREFIX" ] || [ "$HOST_PREFIX" = "$CONTAINER_PREFIX" ]; then + exec "$REAL_DOCKER" "$@" +fi + +# Helper to split quoted values +split_with_quote() { + local value="$1" + local quote="" + if [ "${value#\"}" != "$value" ] && [ "${value%\"}" != "$value" ]; then + quote='"' + value="${value#\"}" + value="${value%\"}" + elif [ "${value#\'}" != "$value" ] && [ "${value%\'}" != "$value" ]; then + quote="'" + value="${value#\'}" + value="${value%\'}" + fi + printf '%s|%s' "$quote" "$value" +} + +# Rewrite volume argument +rewrite_volume_arg() { + local original="$1" + local src_with_quotes="${original%%:*}" + local remainder="${original#"$src_with_quotes"}" + local parsed + parsed="$(split_with_quote "$src_with_quotes")" + local quote="" + local src="" + IFS='|' read -r quote src <<<"$parsed" + if [ -n "$src" ] && { [ "$src" = "$CONTAINER_PREFIX" ] || [ "${src#$CONTAINER_PREFIX/}" != "$src" ]; }; then + local suffix="${src#$CONTAINER_PREFIX}" + local new_src="${HOST_PREFIX}${suffix}" + echo "${quote}${new_src}${quote}${remainder}" + return + fi + echo "$original" +} + +# Rewrite mount argument +rewrite_mount_arg() { + local original="$1" + IFS=',' read -ra parts <<<"$original" + local updated=() + for part in "${parts[@]}"; do + local key="${part%%=*}" + local value="${part#*=}" + if [ "$key" = "source" ] || [ "$key" = "src" ]; then + local parsed + parsed="$(split_with_quote "$value")" + local quote="" + local path="" + IFS='|' read -r quote path <<<"$parsed" + if [ -n "$path" ] && { [ "$path" = "$CONTAINER_PREFIX" ] || [ "${path#$CONTAINER_PREFIX/}" != "$path" ]; }; then + local suffix="${path#$CONTAINER_PREFIX}" + local new_path="${HOST_PREFIX}${suffix}" + part="${key}=${quote}${new_path}${quote}" + fi + fi + updated+=("$part") + done + local IFS=',' + echo "${updated[*]}" +} + +# Adjust all arguments +adjust_args() { + local -a result=() + while [ "$#" -gt 0 ]; do + local arg="$1" + case "$arg" in + -v) + if [ "$#" -gt 1 ]; then + result+=("-v" "$(rewrite_volume_arg "$2")") + shift + else + result+=("$arg") + fi + ;; + -v*) + result+=("-v$(rewrite_volume_arg "${arg#-v}")") + ;; + --volume) + if [ "$#" -gt 1 ]; then + result+=("--volume" "$(rewrite_volume_arg "$2")") + shift + else + result+=("$arg") + fi + ;; + --volume=*) + result+=("--volume=$(rewrite_volume_arg "${arg#--volume=}")") + ;; + --mount) + if [ "$#" -gt 1 ]; then + result+=("--mount" "$(rewrite_mount_arg "$2")") + shift + else + result+=("$arg") + fi + ;; + --mount=*) + result+=("--mount=$(rewrite_mount_arg "${arg#--mount=}")") + ;; + *) + result+=("$arg") + ;; + esac + shift + done + printf '%s\0' "${result[@]}" +} + +# Transform and execute +mapfile -d '' transformed < <(adjust_args "$@") +exec "$REAL_DOCKER" "${transformed[@]}" \ No newline at end of file diff --git a/entrypoint/main.sh b/entrypoint/main.sh index 4526ddd..0891d00 100644 --- a/entrypoint/main.sh +++ b/entrypoint/main.sh @@ -39,6 +39,8 @@ fi run_startup_script +create_group_user + create_amp_user configure_timezone @@ -49,6 +51,10 @@ configure_main_instance configure_release_stream +configure_license + +amp_host_home + if [ "${AMP_AUTO_UPDATE}" = "true" ]; then upgrade_instances else diff --git a/entrypoint/routines.sh b/entrypoint/routines.sh index 89a44d1..f99409f 100644 --- a/entrypoint/routines.sh +++ b/entrypoint/routines.sh @@ -46,9 +46,22 @@ check_data_volume() { echo "Data volume is ok!" } +# Configure file permissions check_file_permissions() { echo "Checking file permissions..." chown -R ${APP_USER}:${APP_GROUP} /home/amp + if [ -w /home/amp ]; then + echo "File permissions set for ${APP_USER}:${APP_GROUP}. Directory /home/amp is writable." + else + echo "Warning: Directory /home/amp is not writable for ${APP_USER}:${APP_GROUP}." + # Attempt to align permissions if ownership alone is insufficient + chmod 755 /home/amp + if [ -w /home/amp ]; then + echo "Permissions updated. Directory /home/amp is now writable for ${APP_USER}:${APP_GROUP}." + else + echo "Warning: Directory /home/amp remains non-writable for ${APP_USER}:${APP_GROUP}." + fi + fi } configure_main_instance() { @@ -102,33 +115,139 @@ configure_timezone() { dpkg-reconfigure --frontend noninteractive tzdata } +create_group_user() { + local AMP_GID="${GID}" + local GROUP_GID="${DOCKER_GID}" + local DOCKER_DESKTOP="" + # Try to detect docker socket GID + if [ -z "${GROUP_GID}" ] && [ -S "/var/run/docker.sock" ]; then + GROUP_GID=$(stat -c '%g' /var/run/docker.sock) + if [ "${GROUP_GID}" = "0" ]; then + echo "Docker socket owned by root group. Not using docker group." + GROUP_GID="" + else + echo "Detected docker socket GID: ${GROUP_GID}" + fi + # try to detect Docker Desktop remote API via DOCKER_HOST + elif [ -z "${GROUP_GID}" ] && [ -n "${DOCKER_HOST}" ]; then + if echo "${DOCKER_HOST}" | grep -qi '^tcp://host\.docker\.internal:2375'; then + echo "Docker Desktop remote host detected via DOCKER_HOST." + DOCKER_DESKTOP="1" + else + echo "DOCKER_HOST is set but does not appear to be Docker Desktop remote API." + fi + else + echo "docker socket and DOCKER_HOST not found. Using default AMP GID: ${AMP_GID}" + fi + + # Docker group detected + if getent group docker > /dev/null 2>&1; then + APP_GROUP=docker + export APP_GROUP + # Docker Desktop remote API detected + elif [ "${DOCKER_DESKTOP}" = "1" ]; then + if ! getent group docker > /dev/null 2>&1; then + echo "Creating docker group for Docker Desktop remote API..." + addgroup --system docker + fi + APP_GROUP=docker + export APP_GROUP + + # GROUP_GID is available + elif [ -n "${GROUP_GID}" ]; then + if ! getent group ${GROUP_GID} > /dev/null 2>&1; then + echo "Creating docker group with GID ${GROUP_GID}..." + addgroup --gid ${GROUP_GID} docker + fi + APP_GROUP=docker + export APP_GROUP + + # AMP_GID is used + else + if ! getent group ${AMP_GID} > /dev/null 2>&1; then + echo "Creating AMP group with GID ${AMP_GID}..." + addgroup --gid ${AMP_GID} amp + fi + APP_GROUP=amp + export APP_GROUP + fi + APP_GID=$(getent group ${APP_GROUP} | awk -F ":" '{ print $3 }') + echo "Group Created: ${APP_GROUP} (${APP_GID})" +} + +# AMP user/group create_amp_user() { - echo "Creating AMP group..." - if [ ! "$(getent group ${GID})" ]; then - # Create group - addgroup \ - --gid ${GID} \ - amp + # Create AMP user + echo "Creating AMP user..." + if ! getent passwd ${UID} > /dev/null 2>&1; then + adduser --uid ${UID} --shell /bin/bash --no-create-home --disabled-password --gecos "" --ingroup ${APP_GROUP} amp + deluser amp users 2>/dev/null || true fi - APP_GROUP=$(getent group ${GID} | awk -F ":" '{ print $1 }') - echo "Group Created: ${APP_GROUP} (${GID})" + APP_USER=$(getent passwd ${UID} | awk -F ":" '{ print $1 }') + echo "User Created: ${APP_USER} (${UID})" + + # Verify user details + echo "Verifying ${APP_USER} user details..." + echo "$(id ${APP_USER})" +} +# AMP user/group +create_amp_user() { + # Create AMP user echo "Creating AMP user..." - if [ ! "$(getent passwd ${UID})" ]; then - # Create user - adduser \ - --uid ${UID} \ - --shell /bin/bash \ - --no-create-home \ - --disabled-password \ - --gecos "" \ - --ingroup ${APP_GROUP} \ - amp + if ! getent passwd ${UID} > /dev/null 2>&1; then + adduser --uid ${UID} --shell /bin/bash --no-create-home --disabled-password --gecos "" --ingroup ${APP_GROUP} amp + deluser amp users 2>/dev/null || true fi APP_USER=$(getent passwd ${UID} | awk -F ":" '{ print $1 }') echo "User Created: ${APP_USER} (${UID})" + + # Verify user details + echo "Verifying AMP user details..." + echo "$(id ${APP_USER})" +} + +# Reactivates the AMP licence across all instances +configure_license() { + if [ -n "${AMP_LICENCE}" ]; then + echo "Reactivating AMP licence across instances." + if run_amp_command_silently "ReactivateAll \"${AMP_LICENCE}\"" >/dev/null 2>&1; then + echo "Licence reactivated successfully." + else + echo "Warning: Failed to reactivate licence." + fi + fi +} + +# Detects and sets AMP_HOST_HOME environment variable +amp_host_home() { + # If AMP_HOST_HOME is already set externally, do nothing. + if [ -n "${AMP_HOST_HOME}" ]; then + return + fi + # Ensure Docker CLI is available + if command -v docker >/dev/null 2>&1; then + # Try to detect the host directory via docker inspect + local docker_name="" + local docker_mount="" + docker_name=$(docker ps --filter "volume=/home/amp" --format "{{.Names}}") + docker_mount=$(docker inspect "$docker_name" --format '{{range .Mounts}}{{if eq .Destination "/home/amp"}}{{.Source}}{{end}}{{end}}' 2>/dev/null || true) + if [ -n "$docker_mount" ]; then + AMP_HOST_HOME="$docker_mount" + export AMP_HOST_HOME + else + echo "Warning: docker inspect did not return a mount source for $docker_name" + fi + fi + # Report the detected host directory + if [ -n "${AMP_HOST_HOME}" ]; then + echo "AMP data mount source: ${AMP_HOST_HOME}" + else + echo "AMP data mount source: unknown" + fi } +# Handles errors during startup handle_error() { # Prints a nice error message and exits. # Usage: handle_error "Error message" @@ -141,6 +260,7 @@ handle_error() { exit 1 } +# Monitors AMP for pending tasks monitor_amp() { # Periodically process pending tasks (e.g. upgrade, reboots, ...) while true; do @@ -149,6 +269,7 @@ monitor_amp() { done } +# Runs user-provided startup script run_startup_script() { # Users may provide their own startup script for installing dependencies, etc. STARTUP_SCRIPT="/home/amp/scripts/startup.sh" @@ -159,6 +280,7 @@ run_startup_script() { fi } +# Shuts down AMP shutdown() { echo "Shutting down... (Signal ${1})" if [ -n "${AMP_STARTED}" ] && [ "${AMP_STARTED}" -eq 1 ] && [ "${1}" != "KILL" ]; then @@ -167,6 +289,7 @@ shutdown() { exit 0 } +# Starts AMP start_amp() { echo "Starting AMP..." run_amp_command "StartBoot" @@ -174,12 +297,14 @@ start_amp() { echo "AMP Started!" } +# Stops AMP stop_amp() { echo "Stopping AMP..." run_amp_command "StopAll" echo "AMP Stopped." } +# Upgrades all instances upgrade_instances() { echo "Upgrading instances..." run_amp_command "UpgradeAll" | consume_progress_bars diff --git a/entrypoint/utils.sh b/entrypoint/utils.sh index 08eb617..4ee4740 100644 --- a/entrypoint/utils.sh +++ b/entrypoint/utils.sh @@ -1,5 +1,6 @@ #!/bin/bash +# Utility functions for AMP Dockerized consume_progress_bars() { # See https://github.com/MitchTalmadge/AMP-dockerized/issues/25#issuecomment-670251321 grep --line-buffered -v -E '\[[-#]+\]' @@ -18,6 +19,7 @@ get_main_instance_name() { fi } +# Check if main instance exists does_main_instance_exist() { local main_name main_name=$(get_main_instance_name) @@ -27,14 +29,17 @@ does_main_instance_exist() { return 0 } +# Run AMP command as APP_USER run_amp_command() { su ${APP_USER} --command "ampinstmgr $1" } +# Run AMP command as APP_USER silently run_amp_command_silently() { su ${APP_USER} --command "ampinstmgr --silent $1" } +# Trap with argument trap_with_arg() { # Credit https://stackoverflow.com/a/2183063/2364405 func="$1" ; shift