From cf8229d1c6ab72a2d8da661bcf756a05070c7c84 Mon Sep 17 00:00:00 2001 From: uprightbass360 Date: Wed, 29 Oct 2025 01:35:09 -0400 Subject: [PATCH] feat: local paths/users --- README.md | 8 +++- build.sh | 41 +++++++++++------ docker-compose.yml | 13 ++++-- scripts/migrate-stack.sh | 4 +- scripts/mysql-entrypoint.sh | 82 +++++++++++++++++++++++++++++++++ scripts/rebuild-with-modules.sh | 9 ++-- scripts/stage-modules.sh | 32 +++++++++++++ setup.sh | 28 +++++------ 8 files changed, 176 insertions(+), 41 deletions(-) create mode 100755 scripts/mysql-entrypoint.sh diff --git a/README.md b/README.md index c5e58bb..f5894b7 100644 --- a/README.md +++ b/README.md @@ -326,7 +326,7 @@ storage/ ├── config/ # Server configuration files ├── logs/ # Server log files ├── modules/ # Module source code and configs -├── mysql-data/ # Database files +├── mysql-data/ # Database files (now under ./local-storage) └── backups/ # Automated database backups ``` @@ -809,6 +809,12 @@ rm -f storage/modules/.requires_rebuild --- +## 🧭 Ownership Hardening TODO + +- [ ] MySQL container: prototype running as `${CONTAINER_USER}` (or via Docker userns remap/custom entrypoint) so shared `${STORAGE_PATH}` data stays user-owned while preserving required init privileges. + +--- + ## 🎯 Next Steps After Installation 1. **Test Client Connection** - Connect with WoW 3.3.5a client using configured realmlist diff --git a/build.sh b/build.sh index 5a7ba64..6fe921f 100755 --- a/build.sh +++ b/build.sh @@ -313,16 +313,14 @@ confirm_build(){ # Module staging logic (extracted from setup.sh) sync_modules(){ local storage_path - storage_path="$(read_env STORAGE_PATH "./storage")" + storage_path="$(read_env STORAGE_PATH_LOCAL "./local-storage")" if [[ "$storage_path" != /* ]]; then + storage_path="${storage_path#./}" storage_path="$ROOT_DIR/$storage_path" fi - info "Synchronising modules (ac-modules container)" - local project_name - project_name="$(resolve_project_name)" - docker compose --project-name "$project_name" -f "$ROOT_DIR/docker-compose.yml" --profile db --profile modules up ac-modules - docker compose --project-name "$project_name" -f "$ROOT_DIR/docker-compose.yml" --profile db --profile modules down >/dev/null 2>&1 || true + mkdir -p "$storage_path/modules" + info "Using local module staging at $storage_path/modules" } resolve_project_name(){ @@ -342,8 +340,9 @@ resolve_project_name(){ stage_modules(){ local src_path="$1" local storage_path - storage_path="$(read_env STORAGE_PATH "./storage")" + storage_path="$(read_env STORAGE_PATH_LOCAL "./local-storage")" if [[ "$storage_path" != /* ]]; then + storage_path="${storage_path#./}" storage_path="$ROOT_DIR/$storage_path" fi @@ -375,8 +374,8 @@ stage_modules(){ export "$module_export_var" done - local host_modules_dir="${storage_path}/modules" - export MODULES_HOST_DIR="$host_modules_dir" + local staging_modules_dir="${storage_path}/modules" + export MODULES_HOST_DIR="$staging_modules_dir" # Set up local storage path for build sentinel tracking local local_storage_path @@ -404,16 +403,28 @@ stage_modules(){ # Run module staging script in local modules directory export MODULES_LOCAL_RUN=1 - if [ -n "$host_modules_dir" ]; then - mkdir -p "$host_modules_dir" - rm -f "$host_modules_dir/.modules_state" "$host_modules_dir/.requires_rebuild" 2>/dev/null || true + if [ -n "$staging_modules_dir" ]; then + mkdir -p "$staging_modules_dir" + rm -f "$staging_modules_dir/.modules_state" "$staging_modules_dir/.requires_rebuild" 2>/dev/null || true fi if (cd "$local_modules_dir" && bash "$ROOT_DIR/scripts/manage-modules.sh"); then ok "Module repositories staged to $local_modules_dir" - if [ -n "$host_modules_dir" ]; then + if [ -n "$staging_modules_dir" ]; then + if command -v rsync >/dev/null 2>&1; then + rsync -a --delete \ + --exclude '.modules_state' \ + --exclude '.requires_rebuild' \ + "$local_modules_dir"/ "$staging_modules_dir"/ + else + find "$staging_modules_dir" -mindepth 1 -maxdepth 1 \ + ! -name '.modules_state' \ + ! -name '.requires_rebuild' \ + -exec rm -rf {} + 2>/dev/null || true + (cd "$local_modules_dir" && tar cf - --exclude='.modules_state' --exclude='.requires_rebuild' .) | (cd "$staging_modules_dir" && tar xf -) + fi if [ -f "$local_modules_dir/.modules_state" ]; then - cp "$local_modules_dir/.modules_state" "$host_modules_dir/.modules_state" 2>/dev/null || true + cp "$local_modules_dir/.modules_state" "$staging_modules_dir/.modules_state" 2>/dev/null || true fi fi else @@ -587,4 +598,4 @@ main(){ show_build_complete } -main "$@" \ No newline at end of file +main "$@" diff --git a/docker-compose.yml b/docker-compose.yml index 8bc818a..a2402c1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -7,6 +7,7 @@ services: profiles: ["db"] image: ${MYSQL_IMAGE} container_name: ${CONTAINER_MYSQL} + userns_mode: "keep-id" environment: MYSQL_ROOT_PASSWORD: ${MYSQL_ROOT_PASSWORD} MYSQL_ROOT_HOST: '${MYSQL_ROOT_HOST}' @@ -19,8 +20,11 @@ services: MYSQL_INNODB_LOG_FILE_SIZE: ${MYSQL_INNODB_LOG_FILE_SIZE} ports: - "${MYSQL_EXTERNAL_PORT}:${MYSQL_PORT}" + entrypoint: + - /usr/local/bin/mysql-entrypoint.sh volumes: - - ${STORAGE_PATH}/mysql-data:/var/lib/mysql-persistent + - ./scripts/mysql-entrypoint.sh:/usr/local/bin/mysql-entrypoint.sh:ro + - ${STORAGE_PATH_LOCAL}/mysql-data:/var/lib/mysql-persistent - ${BACKUP_PATH}:/backups - ${HOST_ZONEINFO_PATH}:/usr/share/zoneinfo:ro tmpfs: @@ -50,6 +54,7 @@ services: image: ${AC_DB_IMPORT_IMAGE} container_name: ${CONTAINER_DB_IMPORT} user: "0:0" + userns_mode: "keep-id" depends_on: ac-mysql: condition: service_healthy @@ -58,7 +63,7 @@ services: volumes: - ${STORAGE_PATH}/config:/azerothcore/env/dist/etc - ${STORAGE_PATH}/logs:/azerothcore/logs - - ${STORAGE_PATH}/mysql-data:/var/lib/mysql-persistent + - ${STORAGE_PATH_LOCAL}/mysql-data:/var/lib/mysql-persistent - ./scripts/db-import-conditional.sh:/tmp/db-import-conditional.sh:ro environment: AC_DATA_DIR: "/azerothcore/data" @@ -94,11 +99,12 @@ services: profiles: ["db"] image: ${MYSQL_IMAGE} container_name: ${CONTAINER_DB_INIT} + userns_mode: "keep-id" depends_on: ac-db-import: condition: service_completed_successfully volumes: - - ${STORAGE_PATH}/mysql-data:/var/lib/mysql-persistent + - ${STORAGE_PATH_LOCAL}/mysql-data:/var/lib/mysql-persistent - ${BACKUP_PATH}:/backups networks: - azerothcore @@ -138,6 +144,7 @@ services: profiles: ["db"] image: ${MYSQL_IMAGE} container_name: ${CONTAINER_BACKUP} + userns_mode: "keep-id" depends_on: ac-db-import: condition: service_completed_successfully diff --git a/scripts/migrate-stack.sh b/scripts/migrate-stack.sh index 256cfbe..0499529 100755 --- a/scripts/migrate-stack.sh +++ b/scripts/migrate-stack.sh @@ -163,8 +163,8 @@ setup_remote_repository(){ exit 1 fi - # Create local-storage directory structure - run_ssh "mkdir -p '$PROJECT_DIR/local-storage/modules'" + # Create local-storage directory structure with proper ownership + run_ssh "mkdir -p '$PROJECT_DIR/local-storage/modules' && chown -R $USER: '$PROJECT_DIR/local-storage'" echo " • Repository synchronized ✓" } diff --git a/scripts/mysql-entrypoint.sh b/scripts/mysql-entrypoint.sh new file mode 100755 index 0000000..941d194 --- /dev/null +++ b/scripts/mysql-entrypoint.sh @@ -0,0 +1,82 @@ +#!/bin/bash +# Wrapper entrypoint to adapt MySQL container UID/GID to match host user expectations. +set -euo pipefail + +ORIGINAL_ENTRYPOINT="${MYSQL_ORIGINAL_ENTRYPOINT:-docker-entrypoint.sh}" +if ! command -v "$ORIGINAL_ENTRYPOINT" >/dev/null 2>&1; then + # Fallback to common install path + if [ -x /usr/local/bin/docker-entrypoint.sh ]; then + ORIGINAL_ENTRYPOINT=/usr/local/bin/docker-entrypoint.sh + fi +fi + +TARGET_SPEC="${MYSQL_RUNTIME_USER:-${CONTAINER_USER:-}}" +if [ -z "${TARGET_SPEC:-}" ] || [ "${TARGET_SPEC}" = "0:0" ]; then + exec "$ORIGINAL_ENTRYPOINT" "$@" +fi + +if [[ "$TARGET_SPEC" != *:* ]]; then + echo "mysql-entrypoint: Expected MYSQL_RUNTIME_USER/CONTAINER_USER in uid:gid form, got '${TARGET_SPEC}'" >&2 + exit 1 +fi + +IFS=':' read -r TARGET_UID TARGET_GID <<< "$TARGET_SPEC" + +if ! [[ "$TARGET_UID" =~ ^[0-9]+$ ]] || ! [[ "$TARGET_GID" =~ ^[0-9]+$ ]]; then + echo "mysql-entrypoint: UID/GID must be numeric (received uid='${TARGET_UID}' gid='${TARGET_GID}')" >&2 + exit 1 +fi + +if ! id mysql >/dev/null 2>&1; then + echo "mysql-entrypoint: mysql user not found in container" >&2 + exit 1 +fi + +current_uid="$(id -u mysql)" +current_gid="$(id -g mysql)" + +# Adjust group if needed +target_group_name="" +if [ "$current_gid" != "$TARGET_GID" ]; then + if groupmod -g "$TARGET_GID" mysql 2>/dev/null; then + target_group_name="mysql" + else + existing_group="$(getent group "$TARGET_GID" | cut -d: -f1 || true)" + if [ -z "$existing_group" ]; then + existing_group="mysql-host" + if ! getent group "$existing_group" >/dev/null 2>&1; then + groupadd -g "$TARGET_GID" "$existing_group" + fi + fi + usermod -g "$existing_group" mysql + target_group_name="$existing_group" + fi +else + target_group_name="$(getent group mysql | cut -d: -f1)" +fi + +if [ -z "$target_group_name" ]; then + target_group_name="$(getent group "$TARGET_GID" | cut -d: -f1 || true)" +fi + +# Adjust user UID if needed +if [ "$current_uid" != "$TARGET_UID" ]; then + if getent passwd "$TARGET_UID" >/dev/null 2>&1 && [ "$(getent passwd "$TARGET_UID" | cut -d: -f1)" != "mysql" ]; then + echo "mysql-entrypoint: UID ${TARGET_UID} already in use by $(getent passwd "$TARGET_UID" | cut -d: -f1)." >&2 + echo "mysql-entrypoint: Please choose a different CONTAINER_USER or adjust the image." >&2 + exit 1 + fi + usermod -u "$TARGET_UID" mysql +fi + +# Ensure group lookup after potential changes +target_group_name="$(getent group "$TARGET_GID" | cut -d: -f1 || echo "$target_group_name")" + +# Update ownership on relevant directories if they exist +for path in /var/lib/mysql-runtime /var/lib/mysql /var/lib/mysql-persistent /backups; do + if [ -e "$path" ]; then + chown -R mysql:"$target_group_name" "$path" + fi +done + +exec "$ORIGINAL_ENTRYPOINT" "$@" diff --git a/scripts/rebuild-with-modules.sh b/scripts/rebuild-with-modules.sh index 7b047e3..df9f7df 100755 --- a/scripts/rebuild-with-modules.sh +++ b/scripts/rebuild-with-modules.sh @@ -168,15 +168,15 @@ REBUILD_SOURCE_PATH="$(realpath "$REBUILD_SOURCE_PATH" 2>/dev/null || echo "$REB # Check for modules in source directory first, then fall back to shared storage LOCAL_MODULES_DIR="$REBUILD_SOURCE_PATH/modules" -SHARED_MODULES_DIR="$STORAGE_PATH/modules" +LOCAL_STAGING_MODULES_DIR="$LOCAL_STORAGE_PATH/modules" if [ -d "$LOCAL_MODULES_DIR" ]; then echo "🔧 Using modules from source directory: $LOCAL_MODULES_DIR" MODULES_DIR="$LOCAL_MODULES_DIR" # Build sentinel always stays in local storage for consistency else - echo "🔧 Using modules from shared storage: $SHARED_MODULES_DIR" - MODULES_DIR="$SHARED_MODULES_DIR" + echo "🔧 Using modules from local staging: $LOCAL_STAGING_MODULES_DIR" + MODULES_DIR="$LOCAL_STAGING_MODULES_DIR" # Build sentinel always stays in local storage for consistency fi @@ -352,9 +352,6 @@ remove_sentinel(){ } remove_sentinel "$SENTINEL_FILE" -if [ -n "$SHARED_MODULES_DIR" ]; then - remove_sentinel "$SHARED_MODULES_DIR/.requires_rebuild" -fi echo "" echo -e "${GREEN}⚔️ Module build forged successfully! ⚔️${NC}" diff --git a/scripts/stage-modules.sh b/scripts/stage-modules.sh index 532223d..6e9aa03 100755 --- a/scripts/stage-modules.sh +++ b/scripts/stage-modules.sh @@ -17,6 +17,37 @@ show_staging_step(){ printf '%b\n' "${YELLOW}🔧 ${step}: ${message}...${NC}" } +sync_local_staging(){ + local src_root="$LOCAL_STORAGE_PATH" + local dest_root="$STORAGE_PATH" + + if [ -z "$src_root" ] || [ -z "$dest_root" ]; then + return + fi + + if [ "$src_root" = "$dest_root" ]; then + return + fi + + local src_modules="${src_root}/modules" + local dest_modules="${dest_root}/modules" + + if [ ! -d "$src_modules" ]; then + echo "ℹ️ No local module staging found at $src_modules (skipping sync)." + return + fi + + echo "📦 Syncing local module staging from $src_modules to $dest_modules" + mkdir -p "$dest_modules" + + if command -v rsync >/dev/null 2>&1; then + rsync -a --delete "$src_modules"/ "$dest_modules"/ + else + find "$dest_modules" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true + (cd "$src_modules" && tar cf - .) | (cd "$dest_modules" && tar xf -) + fi +} + SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_DIR="$(dirname "$SCRIPT_DIR")" ENV_FILE="$PROJECT_DIR/.env" @@ -220,6 +251,7 @@ fi # Stage the services show_staging_step "Service Orchestration" "Preparing realm services" +sync_local_staging echo "🎬 Staging services with profile: services-$TARGET_PROFILE" echo "⏳ Pulling images and starting containers; this can take several minutes on first run." diff --git a/setup.sh b/setup.sh index e4d5b9e..44830c4 100755 --- a/setup.sh +++ b/setup.sh @@ -1088,6 +1088,11 @@ fi local LOCAL_STORAGE_ROOT="${STORAGE_PATH_LOCAL:-./local-storage}" LOCAL_STORAGE_ROOT="${LOCAL_STORAGE_ROOT%/}" [ -z "$LOCAL_STORAGE_ROOT" ] && LOCAL_STORAGE_ROOT="." + local LOCAL_STORAGE_ROOT_ABS="$LOCAL_STORAGE_ROOT" + if [[ "$LOCAL_STORAGE_ROOT_ABS" != /* ]]; then + LOCAL_STORAGE_ROOT_ABS="$SCRIPT_DIR/${LOCAL_STORAGE_ROOT_ABS#./}" + fi + LOCAL_STORAGE_ROOT_ABS="${LOCAL_STORAGE_ROOT_ABS%/}" STORAGE_PATH_LOCAL="$LOCAL_STORAGE_ROOT" export STORAGE_PATH STORAGE_PATH_LOCAL @@ -1107,11 +1112,7 @@ fi fi # Set build sentinel to indicate rebuild is needed - local storage_abs="$STORAGE_PATH_LOCAL" - if [[ "$storage_abs" != /* ]]; then - storage_abs="$(cd "$(dirname "$0")" && pwd)/$storage_abs" - fi - local sentinel="$storage_abs/modules/.requires_rebuild" + local sentinel="$LOCAL_STORAGE_ROOT_ABS/modules/.requires_rebuild" mkdir -p "$(dirname "$sentinel")" touch "$sentinel" say INFO "Build sentinel created at $sentinel" @@ -1362,13 +1363,10 @@ ALPINE_IMAGE=$DEFAULT_ALPINE_IMAGE EOF } > "$ENV_OUT" - local storage_abs_path="${STORAGE_PATH:-$DEFAULT_LOCAL_STORAGE}" - if [[ "$storage_abs_path" != /* ]]; then - storage_abs_path="$(pwd)/${storage_abs_path#./}" - fi - storage_abs_path="${storage_abs_path%/}" - local host_modules_dir="${storage_abs_path}/modules" - mkdir -p "$host_modules_dir" + local staging_modules_dir="${LOCAL_STORAGE_ROOT_ABS}/modules" + mkdir -p "$staging_modules_dir" + local local_mysql_data_dir="${LOCAL_STORAGE_ROOT_ABS}/mysql-data" + mkdir -p "$local_mysql_data_dir" local -a MODULE_STATE_VARS=( MODULE_PLAYERBOTS MODULE_AOE_LOOT MODULE_LEARN_SPELLS MODULE_FIREWORKS MODULE_INDIVIDUAL_PROGRESSION @@ -1386,8 +1384,10 @@ EOF local module_value="${!module_state_var:-0}" module_state_string+="${module_state_var}=${module_value}|" done - printf '%s' "$module_state_string" > "${host_modules_dir}/.modules_state" - rm -f "${host_modules_dir}/.requires_rebuild" 2>/dev/null || true + printf '%s' "$module_state_string" > "${staging_modules_dir}/.modules_state" + if [ "$NEEDS_CXX_REBUILD" != "1" ]; then + rm -f "${staging_modules_dir}/.requires_rebuild" 2>/dev/null || true + fi say SUCCESS ".env written to $ENV_OUT" show_realm_configured