#!/bin/bash # Robot Dev Team Project # File: gitlab/docker-entrypoint-rdt.sh # Description: Entrypoint wrapper for GitLab CE that performs one-time RDT setup. # License: MIT # SPDX-License-Identifier: MIT # Copyright (c) 2025 MCKNLY LLC # # Wraps the standard GitLab init command (/assets/init-container) to perform # first-boot setup automatically: # # 1. Waits for GitLab to report healthy (/-/health). # 2. Reads the auto-generated root password from # /etc/gitlab/initial_root_password (written by GitLab on first boot). # 3. Creates an admin Personal Access Token (api scope) via # gitlab-rails runner and writes it to /etc/gitlab/rdt.env. # 4. Enables outbound requests to local network addresses via the admin API # (required so the File Hook can register webhooks that resolve to Docker # gateway IPs such as host.docker.internal / 172.x.x.x). # # Design: the setup logic runs in a background subshell spawned before # exec'ing /assets/init-container. exec replaces this script as PID 1 so # GitLab's init process behaves exactly as it would without this wrapper. # The background subshell is reparented to PID 1 and continues running # independently while GitLab boots. # # On subsequent container starts the setup is skipped because the marker # file /etc/gitlab/.rdt-setup-done already exists. # # If /etc/gitlab/initial_root_password is absent (i.e. a custom # GITLAB_ROOT_PASSWORD was set), automatic PAT creation is skipped and a # warning is printed. In that case, create the PAT manually, set # GITLAB_ADMIN_TOKEN in gitlab/.env, and restart the container. set -eo pipefail SETUP_MARKER="/etc/gitlab/.rdt-setup-done" RDT_ENV_FILE="/etc/gitlab/rdt.env" GITLAB_URL="http://localhost" # --------------------------------------------------------------------------- # Spawn the setup watcher in the background BEFORE exec'ing the real init. # Once exec runs, this script is gone and the subshell is a child of PID 1. # --------------------------------------------------------------------------- if [[ ! -f "$SETUP_MARKER" ]]; then ( echo "[RDT] Waiting for GitLab to become healthy before running first-time setup..." until curl -sf "$GITLAB_URL/-/health" > /dev/null 2>&1; do sleep 10 done echo "[RDT] GitLab is healthy. Starting first-time setup..." PASS_FILE="/etc/gitlab/initial_root_password" if [[ ! -f "$PASS_FILE" ]]; then # Custom GITLAB_ROOT_PASSWORD was set — no auto-generated password file. echo "[RDT] WARNING: $PASS_FILE not found." echo "[RDT] This usually means a custom GITLAB_ROOT_PASSWORD was configured." echo "[RDT] To enable automatic webhook provisioning:" echo "[RDT] 1. Create an admin PAT (api scope) in the GitLab web UI." echo "[RDT] 2. Set GITLAB_ADMIN_TOKEN in gitlab/.env." echo "[RDT] 3. Restart the container." echo "[RDT] Alternatively, run: bash gitlab/setup-gitlab.sh" exit 0 fi # ----------------------------------------------------------------------- # Create (or retrieve) the admin PAT via Rails runner. # find_or_initialize_by makes this idempotent: re-running against an # existing 'rdt-admin' token updates it rather than creating a duplicate. # GitLab 17+ requires a non-null expiration date; use 365 days from today. # ----------------------------------------------------------------------- echo "[RDT] Creating admin Personal Access Token..." # Disable set -e for the rails runner call so we can capture and report # any failure rather than silently exiting the background subshell. set +e RUNNER_OUT=$(gitlab-rails runner " user = User.find_by_username('root') pat = user.personal_access_tokens.find_or_initialize_by(name: 'rdt-admin') pat.scopes = ['api'] pat.expires_at = Date.today + 365 pat.save! puts pat.token " 2>&1) RUNNER_EXIT=$? set -e if [[ $RUNNER_EXIT -ne 0 ]]; then echo "[RDT] ERROR: gitlab-rails runner exited with status $RUNNER_EXIT." echo "$RUNNER_OUT" | grep -iE "error|invalid|exception|validation" | head -5 echo "[RDT] File Hook webhook provisioning will be unavailable until a valid" echo "[RDT] GITLAB_ADMIN_TOKEN is written to $RDT_ENV_FILE or set in gitlab/.env." exit 0 fi TOKEN=$(echo "$RUNNER_OUT" | tail -n1) if [[ -z "$TOKEN" || ! "$TOKEN" =~ ^glpat- ]]; then echo "[RDT] WARNING: PAT creation returned unexpected output: '${TOKEN}'." echo "[RDT] File Hook webhook provisioning will be unavailable until a valid" echo "[RDT] GITLAB_ADMIN_TOKEN is written to $RDT_ENV_FILE or set in gitlab/.env." exit 0 fi echo "[RDT] Admin PAT created. Writing to $RDT_ENV_FILE..." echo "GITLAB_ADMIN_TOKEN=$TOKEN" > "$RDT_ENV_FILE" # Readable by root and the git user (which runs Sidekiq and file hooks). chown root:git "$RDT_ENV_FILE" chmod 640 "$RDT_ENV_FILE" # ----------------------------------------------------------------------- # Enable outbound requests to local/private network addresses. # GitLab blocks these by default; the setting is stored in the database # and cannot be configured in gitlab.rb — it must be applied via the API. # ----------------------------------------------------------------------- echo "[RDT] Enabling outbound requests to local network addresses..." RESULT=$(curl -sf -X PUT \ -H "PRIVATE-TOKEN: $TOKEN" \ "$GITLAB_URL/api/v4/application/settings" \ --data "allow_local_requests_from_web_hooks_and_services=true" \ --data "allow_local_requests_from_system_hooks=true") ACTUAL=$(echo "$RESULT" | python3 -c \ "import sys,json; s=json.load(sys.stdin); print(s.get('allow_local_requests_from_web_hooks_and_services','?'))" \ 2>/dev/null || echo "?") if [[ "$ACTUAL" == "True" ]]; then echo "[RDT] Local network webhook delivery enabled." else echo "[RDT] WARNING: Setting may not have applied (got: $ACTUAL)." echo "[RDT] Check manually in Admin > Settings > Network > Outbound requests." fi touch "$SETUP_MARKER" echo "[RDT] First-time setup complete." ) & fi # --------------------------------------------------------------------------- # Hand off to the standard GitLab init as PID 1. # exec replaces this script in-place so /assets/init-container runs exactly # as it would without the wrapper. # --------------------------------------------------------------------------- exec /assets/init-container