Files
qt6-migration-pipeline/app/run_docker.sh
T

347 lines
10 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
IMAGE_NAME="${IMAGE_NAME:-migration-pipeline:ubuntu22.04}"
CONTAINER_USER="${CONTAINER_USER:-${USER:-runner}}"
CONTAINER_GROUP="${CONTAINER_GROUP:-$CONTAINER_USER}"
ENABLE_CONTAINER_SUDO="${ENABLE_CONTAINER_SUDO:-1}"
QT_ROOT_DEFAULT="${QT_ROOT_DEFAULT:-/opt/Qt/6.8.3/gcc_64}"
GIT_REPO_DIR=""
WORKDIR_IN_CONTAINER=""
RUN_QT_SMOKE_TEST=0
RUN_RPM=0
PROJECT_RPM=""
OS_VERSION_RPM="ubuntu24"
LTS_VERSION_RPM="1.2.0"
GUEZ_MODE_RPM="ON"
QT_ROOT_OVERRIDE=""
DOCKER_ARGS=()
CMD_ARGS=()
usage() {
cat <<'EOF'
Usage:
./run_docker.sh [options] [repo_dir] [-- command args...]
Options:
--no-sudo Disable passwordless sudo for the container user
--workdir PATH Set working directory inside container
--qt-smoke-test Run a Qt/CMake smoke test inside the container
--qt-path PATH Override Qt root path inside container
-h, --help Show this help
Examples:
./run_docker.sh
./run_docker.sh /path/to/repo
./run_docker.sh /path/to/repo -- bash
./run_docker.sh --qt-smoke-test /path/to/repo
./run_docker.sh --qt-path /opt/Qt/6.8.3/gcc_64 /path/to/repo -- bash
EOF
}
while [[ $# -gt 0 ]]; do
case "$1" in
--no-sudo)
ENABLE_CONTAINER_SUDO=0
shift
;;
--workdir)
WORKDIR_IN_CONTAINER="$2"
shift 2
;;
--qt-smoke-test)
RUN_QT_SMOKE_TEST=1
shift
;;
--qt-path)
QT_ROOT_OVERRIDE="$2"
shift 2
;;
--run-rpm)
RUN_RPM=1
shift
;;
--rpm-project)
PROJECT_RPM="$2"
shift 2
;;
--rpm-os)
OS_VERSION_RPM="$2"
shift 2
;;
--rpm-lts)
LTS_VERSION_RPM="$2"
shift 2
;;
--rpm-guez)
GUEZ_MODE_RPM="$2"
shift 2
;;
-h|--help)
usage
exit 0
;;
--)
shift
CMD_ARGS=("$@")
break
;;
-*)
echo "Unknown option: $1" >&2
usage >&2
exit 1
;;
*)
if [[ -z "$GIT_REPO_DIR" ]]; then
GIT_REPO_DIR="$1"
else
CMD_ARGS+=("$1")
fi
shift
;;
esac
done
HOST_UID="$(id -u)"
HOST_GID="$(id -g)"
add_optional_mount() {
local host_path="$1"
local container_path="$2"
if [[ -e "$host_path" ]]; then
DOCKER_ARGS+=( -v "$host_path:$container_path" )
fi
}
add_optional_mount "$HOME/.gitconfig" "/home/$CONTAINER_USER/.gitconfig"
add_optional_mount "$HOME/.ssh" "/home/$CONTAINER_USER/.ssh"
add_optional_mount "$HOME/.bashrc" "/home/$CONTAINER_USER/.bashrc"
add_optional_mount "$HOME/.bash_aliases" "/home/$CONTAINER_USER/.bash_aliases"
if [[ -n "$GIT_REPO_DIR" ]]; then
if [[ ! -d "$GIT_REPO_DIR" ]]; then
echo "Repository directory does not exist: $GIT_REPO_DIR" >&2
exit 1
fi
GIT_REPO_DIR="$(cd "$GIT_REPO_DIR" && pwd)"
DOCKER_ARGS+=( -v "$GIT_REPO_DIR:/repo" )
if [[ -d "$GIT_REPO_DIR/src" ]]; then
DOCKER_ARGS+=( -v "$GIT_REPO_DIR/src:/src" )
fi
# Prefer ./externals (plural) as requested; fall back to ./external
if [[ -d "$GIT_REPO_DIR/externals" ]]; then
DOCKER_ARGS+=( -v "$GIT_REPO_DIR/externals:/repo/external" )
echo "Mounting repo externals from $GIT_REPO_DIR/externals -> /repo/external"
elif [[ -d "$GIT_REPO_DIR/external" ]]; then
DOCKER_ARGS+=( -v "$GIT_REPO_DIR/external:/repo/external" )
echo "Mounting repo external from $GIT_REPO_DIR/external -> /repo/external"
fi
if [[ -z "$WORKDIR_IN_CONTAINER" ]]; then
WORKDIR_IN_CONTAINER="/repo"
fi
fi
if [[ -z "$WORKDIR_IN_CONTAINER" ]]; then
WORKDIR_IN_CONTAINER="/app"
fi
if [[ -z "$QT_ROOT_OVERRIDE" ]]; then
QT_ROOT_OVERRIDE="$QT_ROOT_DEFAULT"
fi
# Name containers with a readable timestamped name so they can be inspected if orphaned
CONTAINER_NAME="${CONTAINER_NAME:-migration-pipeline-$(date +%s)-$HOST_UID}"
DOCKER_NAME_FLAG="--name ${CONTAINER_NAME}"
echo "Launching container with name: ${CONTAINER_NAME}"
if [[ ! -f "$SCRIPT_DIR/.env" ]]; then
echo "Missing env file: $SCRIPT_DIR/.env" >&2
exit 1
fi
docker build -t "$IMAGE_NAME" "$SCRIPT_DIR"
if [[ $RUN_QT_SMOKE_TEST -eq 1 ]]; then
CMD_ARGS=(
bash -lc "
set -euo pipefail
echo '== User =='
whoami
echo '== Qt path =='
echo \"$QT_ROOT_OVERRIDE\"
test -d \"$QT_ROOT_OVERRIDE\"
echo '== CMake version =='
cmake --version
echo '== Qt config =='
find \"$QT_ROOT_OVERRIDE\" -maxdepth 3 \\( -name 'Qt6Config.cmake' -o -name 'Qt6CoreConfig.cmake' \\) | sort
TMPDIR=\$(mktemp -d)
cat > \"\$TMPDIR/CMakeLists.txt\" <<'EOF'
cmake_minimum_required(VERSION 3.21)
project(QtSmokeTest LANGUAGES CXX)
find_package(Qt6 REQUIRED COMPONENTS Core)
add_executable(qt_smoke main.cpp)
target_link_libraries(qt_smoke PRIVATE Qt6::Core)
EOF
cat > \"\$TMPDIR/main.cpp\" <<'EOF'
#include <QCoreApplication>
#include <QString>
#include <iostream>
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
std::cout << QString(\"Qt smoke test OK\").toStdString() << std::endl;
return 0;
}
EOF
cmake -S \"\$TMPDIR\" -B \"\$TMPDIR/build\" -G Ninja -DCMAKE_PREFIX_PATH=\"$QT_ROOT_OVERRIDE\"
cmake --build \"\$TMPDIR/build\"
\"\$TMPDIR/build/qt_smoke\"
"
)
fi
if [[ ${RUN_RPM} -eq 1 ]]; then
# Run rpm prepare script inside container
if [[ -n "$PROJECT_RPM" ]]; then
CMD_ARGS=(bash -lc "/app/prepare_rpm.sh '$PROJECT_RPM' '$OS_VERSION_RPM' '$LTS_VERSION_RPM' '$GUEZ_MODE_RPM'")
else
CMD_ARGS=(bash -lc "/app/prepare_rpm.sh")
fi
elif [[ ${#CMD_ARGS[@]} -eq 0 ]]; then
CMD_ARGS=(bash)
fi
if [ -t 1 ]; then
DOCKER_TTY_FLAG="-it"
else
DOCKER_TTY_FLAG="-i"
fi
docker run --rm $DOCKER_TTY_FLAG $DOCKER_NAME_FLAG \
--user root \
--env-file "$SCRIPT_DIR/.env" \
-v "$SCRIPT_DIR:/app" \
"${DOCKER_ARGS[@]}" \
-w "$WORKDIR_IN_CONTAINER" \
-e LOCAL_UID="$HOST_UID" \
-e LOCAL_GID="$HOST_GID" \
-e LOCAL_USER="$CONTAINER_USER" \
-e LOCAL_GROUP="$CONTAINER_GROUP" \
-e ENABLE_CONTAINER_SUDO="$ENABLE_CONTAINER_SUDO" \
-e WORKDIR_IN_CONTAINER="$WORKDIR_IN_CONTAINER" \
-e QT_ROOT="$QT_ROOT_OVERRIDE" \
-e Qt6_ROOT="$QT_ROOT_OVERRIDE" \
-e EXTERNAL_DIR="/repo/external" \
-e CMAKE_PREFIX_PATH="$QT_ROOT_OVERRIDE:/repo/external" \
-e PROJECT_BASE_DIR="${PROJECT_BASE_DIR:-./}" \
"$IMAGE_NAME" \
bash -s -- "${CMD_ARGS[@]}" <<'DOCKER_SCRIPT'
set -euo pipefail
ensure_group() {
# If a group with the requested GID exists, use that group name.
if getent group "${LOCAL_GID}" >/dev/null 2>&1; then
getent group "${LOCAL_GID}" | cut -d: -f1
return
fi
# If the desired group name exists, try to change its GID if unused.
if getent group "${LOCAL_GROUP}" >/dev/null 2>&1; then
existing_gid=$(getent group "${LOCAL_GROUP}" | cut -d: -f3)
if [[ "${existing_gid}" != "${LOCAL_GID}" ]]; then
if ! getent group "${LOCAL_GID}" >/dev/null 2>&1; then
groupmod -g "${LOCAL_GID}" "${LOCAL_GROUP}" || true
else
echo "Warning: desired GID ${LOCAL_GID} already in use by another group; keeping ${LOCAL_GROUP} gid ${existing_gid}" >&2
fi
fi
echo "${LOCAL_GROUP}"
return
fi
# Otherwise create the group with requested GID, unless GID is taken.
if getent group "${LOCAL_GID}" >/dev/null 2>&1; then
# Some other group holds the target GID; return that group's name.
getent group "${LOCAL_GID}" | cut -d: -f1
else
groupadd -g "${LOCAL_GID}" "${LOCAL_GROUP}"
echo "${LOCAL_GROUP}"
fi
}
ensure_user() {
# If a user with the desired UID already exists, use that user instead
if getent passwd "${LOCAL_UID}" >/dev/null 2>&1; then
existing_user=$(getent passwd "${LOCAL_UID}" | cut -d: -f1)
echo "Host UID ${LOCAL_UID} already exists in container as user '${existing_user}'; will use that account."
LOCAL_USER="${existing_user}"
return
fi
# If the desired username already exists, try to change its UID (only if the target UID is free)
if id -u "${LOCAL_USER}" >/dev/null 2>&1; then
current_uid=$(id -u "${LOCAL_USER}")
if [[ "${current_uid}" != "${LOCAL_UID}" ]]; then
if ! getent passwd "${LOCAL_UID}" >/dev/null 2>&1; then
usermod -u "${LOCAL_UID}" "${LOCAL_USER}" || echo "Warning: cannot change UID for ${LOCAL_USER}, leaving as ${current_uid}" >&2
else
echo "Warning: target UID ${LOCAL_UID} already in use; leaving user ${LOCAL_USER} with UID ${current_uid}" >&2
fi
fi
else
# Create the user only if the target UID is free
if getent passwd "${LOCAL_UID}" >/dev/null 2>&1; then
# Someone owns the UID, pick that user
existing_user=$(getent passwd "${LOCAL_UID}" | cut -d: -f1)
echo "Host UID ${LOCAL_UID} in use by '${existing_user}'; using that user inside container."
LOCAL_USER="${existing_user}"
else
useradd -m -u "${LOCAL_UID}" -g "${LOCAL_GID}" -s /bin/bash "${LOCAL_USER}" || {
echo "Warning: failed to create user ${LOCAL_USER} with UID ${LOCAL_UID}; falling back to root inside container." >&2
LOCAL_USER=root
}
fi
fi
# Ensure home dir exists and is owned by requested uid/gid where possible
mkdir -p "/home/${LOCAL_USER}"
chown -R "${LOCAL_UID}:${LOCAL_GID}" "/home/${LOCAL_USER}" 2>/dev/null || true
}
setup_sudo() {
if [[ "${ENABLE_CONTAINER_SUDO}" == "1" ]]; then
usermod -aG sudo "${LOCAL_USER}" || true
echo "${LOCAL_USER} ALL=(ALL) NOPASSWD:ALL" > /etc/sudoers.d/90-local-user
chmod 0440 /etc/sudoers.d/90-local-user
else
rm -f /etc/sudoers.d/90-local-user
fi
}
fix_mount_ownership() {
mkdir -p "/home/${LOCAL_USER}/.ssh"
chown -R "${LOCAL_UID}:${LOCAL_GID}" "/home/${LOCAL_USER}/.ssh" 2>/dev/null || true
chown "${LOCAL_UID}:${LOCAL_GID}" "/home/${LOCAL_USER}/.gitconfig" 2>/dev/null || true
chown "${LOCAL_UID}:${LOCAL_GID}" "/home/${LOCAL_USER}/.bashrc" 2>/dev/null || true
chown "${LOCAL_UID}:${LOCAL_GID}" "/home/${LOCAL_USER}/.bash_aliases" 2>/dev/null || true
}
ensure_group >/dev/null
ensure_user
setup_sudo
fix_mount_ownership
export HOME="/home/${LOCAL_USER}"
cd "${WORKDIR_IN_CONTAINER}"
exec runuser -u "${LOCAL_USER}" -- "$@"
DOCKER_SCRIPT