333 lines
9.3 KiB
Bash
Executable File
333 lines
9.3 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
|
|
|
|
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
|
|
|
|
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 \
|
|
--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
|