#!/usr/bin/env bash set -euo pipefail PROG="init_gitlab" SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" # shellcheck disable=SC1090 source "$SCRIPT_DIR/prole_cfg.sh" MODE="$(prole_normalize_mode "${PROLE_MODE:-${DEPLOYMENT_MODE:-k3d}}")" NAMESPACE="${GITLAB_NAMESPACE:-}" CFG_PATH="" FORCE=0 NODE_SELECTOR="${GITLAB_NODE_SELECTOR:-${NODE_SELECTOR:-gandalf.prole.org}}" usage() { cat < Deployment mode (default: ${MODE:-k3d}) -n, --namespace Target namespace (default: gitlab) -c, --config Path to prole.cfg (defaults to detected) --node-selector Node to pin GitLab workloads (default: gandalf.prole.org) --force Remove existing GitLab and Gitea releases before deploy --help Show this help Behavior: - Removes any pre-existing git.prole.org Gitea configurations (helm release, namespace, Kong routes) when --force is specified or when gitea is detected. - Installs the GitLab Operator via Helm into the gitlab namespace. - Provisions a GitLab CR that uses the knoe-db CloudNativePG cluster as its external PostgreSQL data store. - Configures git.prole.org as a Kong-managed ingress pointing to GitLab. - Pins all GitLab workloads to the selected node (default: gandalf.prole.org). EOF } die() { echo "[ERROR] $*" >&2; exit 2; } log() { echo "[INFO] $*"; } warn() { echo "[WARN] $*" >&2; } while [[ $# -gt 0 ]]; do case "$1" in --mode) MODE="$(prole_normalize_mode "${2:-}")"; shift 2 ;; --mode=*) MODE="$(prole_normalize_mode "${1#*=}")"; shift 1 ;; -n|--namespace) NAMESPACE="${2:-}"; shift 2 ;; --namespace=*) NAMESPACE="${1#*=}"; shift 1 ;; -c|--config) CFG_PATH="${2:-}"; shift 2 ;; --config=*) CFG_PATH="${1#*=}"; shift 1 ;; --node-selector) NODE_SELECTOR="${2:-}"; shift 2 ;; --node-selector=*) NODE_SELECTOR="${1#*=}"; shift 1 ;; --force) FORCE=1; shift 1 ;; -h|--help) usage; exit 0 ;; *) break ;; esac done # --------------------------------------------------------------------------- # Resolve config path # --------------------------------------------------------------------------- if [[ -z "$CFG_PATH" && -n "${PROLE_CONF:-}" && -f "${PROLE_CONF}/prole.cfg" ]]; then CFG_PATH="${PROLE_CONF}/prole.cfg" elif [[ -z "$CFG_PATH" && -f "$SCRIPT_DIR/../conf/prole.cfg" ]]; then CFG_PATH="$SCRIPT_DIR/../conf/prole.cfg" fi if [[ -z "$NAMESPACE" && -n "$CFG_PATH" ]]; then NAMESPACE="$(_prole_cfg_extract_key "$CFG_PATH" "GITLAB_NAMESPACE")" fi NAMESPACE="${NAMESPACE:-gitlab}" export GITLAB_NAMESPACE="$NAMESPACE" case "$MODE" in k3d|k3s|k8s|local) ;; *) die "Unsupported mode '$MODE' (use k3d, k3s, k8s, or local)" ;; esac export PROLE_MODE="$MODE" command -v kubectl >/dev/null || die "kubectl not found" command -v helm >/dev/null || die "helm not found (required for GitLab Operator install)" # --------------------------------------------------------------------------- # knoe-db (CNPG) settings — reuse the same cluster gitea uses # --------------------------------------------------------------------------- DB_NAMESPACE="${KNOE_DB_NAMESPACE:-${DATABASE_NAMESPACE:-knoe-db}}" CNPG_CLUSTER_NAME="${CNPG_CLUSTER_NAME:-${CLUSTER_NAME:-knoe-db}}" GITLAB_DB_NAME="${GITLAB_DB_NAME:-gitlabhq_production}" GITLAB_DB_USER="${GITLAB_DB_USER:-gitlab}" GITLAB_DB_PASSWORD="${GITLAB_DB_PASSWORD:-}" DB_HOST="${CNPG_CLUSTER_NAME}-rw.${DB_NAMESPACE}.svc.cluster.local" DB_PORT="${GITLAB_DB_PORT:-5432}" # GitLab domain (reuses the git.prole.org shared hostname) GITLAB_DOMAIN="${GITLAB_DOMAIN:-git.prole.org}" # Google Workspace OIDC via knoe-auth — set FRONTDOOR_HOST to enable FRONTDOOR_HOST="${FRONTDOOR_HOST:-}" GITLAB_RELEASE="gitlab" # External Redis — reuse the shared instance in knoe-system REDIS_HOST="${REDIS_HOST:-redis-master.knoe-system.svc.cluster.local}" REDIS_PORT="${REDIS_PORT:-6379}" # --------------------------------------------------------------------------- # Operator chart coordinates # --------------------------------------------------------------------------- OPERATOR_REPO_NAME="gitlab-operator" OPERATOR_REPO_URL="https://gitlab.com/api/v4/projects/18899486/packages/helm/stable" OPERATOR_CHART="gitlab-operator/gitlab-operator" OPERATOR_RELEASE="gitlab-operator" OPERATOR_NAMESPACE="${GITLAB_OPERATOR_NAMESPACE:-${NAMESPACE}}" # GitLab chart version pinned inside the CR; leave empty to use operator default GITLAB_CHART_VERSION="${GITLAB_CHART_VERSION:-}" # --------------------------------------------------------------------------- # Helper: resolve knoe-db primary pod (same pattern as init_gitea.sh) # --------------------------------------------------------------------------- resolve_knoe_db_primary_pod() { local primary primary=$(kubectl -n "$DB_NAMESPACE" get pods \ -l "cnpg.io/cluster=${CNPG_CLUSTER_NAME},cnpg.io/instanceRole=primary" \ -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) if [[ -z "$primary" ]]; then primary=$(kubectl -n "$DB_NAMESPACE" get pods \ -l "cnpg.io/cluster=${CNPG_CLUSTER_NAME}" \ -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) fi printf '%s' "$primary" } sql_escape_literal() { printf '%s' "${1:-}" | sed "s/'/''/g" } # --------------------------------------------------------------------------- # Resolve GitLab DB password (pull from knoe-db-superuser secret as fallback) # --------------------------------------------------------------------------- resolve_gitlab_db_password() { if [[ -n "${GITLAB_DB_PASSWORD:-}" ]]; then return 0 fi local resolved="" if kubectl -n "$DB_NAMESPACE" get secret knoe-db-superuser >/dev/null 2>&1; then resolved=$(kubectl -n "$DB_NAMESPACE" get secret knoe-db-superuser \ -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || true) fi if [[ -n "$resolved" ]]; then GITLAB_DB_PASSWORD="$resolved" else # Generate a random password when none is available GITLAB_DB_PASSWORD="$(LC_ALL=C tr -dc 'A-Za-z0-9' /dev/null | head -c 32 || true)" warn "Generated random GitLab DB password; store it in GITLAB_DB_PASSWORD for future runs." fi } # --------------------------------------------------------------------------- # Provision GitLab role + database in knoe-db # --------------------------------------------------------------------------- setup_knoe_db_for_gitlab() { local primary primary="$(resolve_knoe_db_primary_pod)" if [[ -z "$primary" ]]; then warn "No knoe-db pod found in namespace '${DB_NAMESPACE}'; skipping GitLab DB setup." return 0 fi resolve_gitlab_db_password local admin_user="" local candidate for candidate in postgres root; do if kubectl -n "$DB_NAMESPACE" exec "$primary" -c postgres -- \ psql -U "$candidate" -d postgres -tAc "SELECT 1" >/dev/null 2>&1; then admin_user="$candidate" break fi done if [[ -z "$admin_user" ]]; then warn "Unable to connect to knoe-db as admin; skipping GitLab DB setup." return 0 fi local esc_pw esc_pw="$(sql_escape_literal "$GITLAB_DB_PASSWORD")" kubectl -n "$DB_NAMESPACE" exec "$primary" -c postgres -- \ psql -U "$admin_user" -d postgres -c " DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='${GITLAB_DB_USER}') THEN CREATE ROLE ${GITLAB_DB_USER} LOGIN PASSWORD '${esc_pw}'; ELSE ALTER ROLE ${GITLAB_DB_USER} WITH PASSWORD '${esc_pw}'; END IF; END \$\$; " >/dev/null 2>&1 || warn "Could not create/update role '${GITLAB_DB_USER}'." local db_exists db_exists=$(kubectl -n "$DB_NAMESPACE" exec "$primary" -c postgres -- \ psql -U "$admin_user" -d postgres -tAc \ "SELECT 1 FROM pg_database WHERE datname='${GITLAB_DB_NAME}';" 2>/dev/null || true) if [[ "$db_exists" != "1" ]]; then kubectl -n "$DB_NAMESPACE" exec "$primary" -c postgres -- \ psql -U "$admin_user" -d postgres -c \ "CREATE DATABASE ${GITLAB_DB_NAME} OWNER ${GITLAB_DB_USER};" \ >/dev/null 2>&1 || warn "Could not create database '${GITLAB_DB_NAME}'." fi log "GitLab database '${GITLAB_DB_NAME}' prepared in knoe-db (ns=${DB_NAMESPACE})." } # --------------------------------------------------------------------------- # Clean up any pre-existing Gitea / git.prole.org configurations # --------------------------------------------------------------------------- cleanup_gitea() { log "Checking for pre-existing Gitea deployment to remove before GitLab install..." local gitea_ns="${GITEA_NAMESPACE:-gitea}" # Remove Gitea Helm release if helm -n "$gitea_ns" status gitea >/dev/null 2>&1; then log "Uninstalling Gitea Helm release from namespace '$gitea_ns'..." helm -n "$gitea_ns" uninstall gitea >/dev/null 2>&1 || true fi # Remove any leftover raw Gitea resources kubectl -n "$gitea_ns" delete deploy/gitea svc/gitea-http svc/gitea-ssh \ >/dev/null 2>&1 || true # Remove Kong routes/services registered for git.prole.org (best-effort) local kong_ns="${KONG_NAMESPACE:-${NAMESPACE:-kong}}" for res_type in kongplugins kongingresses; do kubectl -n "$gitea_ns" delete "$res_type" --all >/dev/null 2>&1 || true done # Remove gitea namespace Ingress objects that route git.prole.org kubectl -n "$gitea_ns" delete ingress \ -l "app.kubernetes.io/name=gitea" >/dev/null 2>&1 || true kubectl -n "$gitea_ns" delete ingress \ --field-selector="metadata.name=gitea" >/dev/null 2>&1 || true log "Gitea cleanup complete." } # --------------------------------------------------------------------------- # Ensure gitlab namespace exists # --------------------------------------------------------------------------- kubectl get ns "$NAMESPACE" >/dev/null 2>&1 || kubectl create namespace "$NAMESPACE" >/dev/null # --------------------------------------------------------------------------- # --force: remove existing GitLab operator and release first # --------------------------------------------------------------------------- if [[ "$FORCE" -eq 1 ]]; then warn "--force: removing existing GitLab resources in namespace '$NAMESPACE'..." if kubectl get crd gitlabs.apps.gitlab.com >/dev/null 2>&1; then kubectl -n "$NAMESPACE" delete gitlab "$GITLAB_RELEASE" >/dev/null 2>&1 || true # Wait briefly for operator to clean up managed resources sleep 10 fi helm -n "$NAMESPACE" uninstall "$GITLAB_RELEASE" >/dev/null 2>&1 || true helm -n "$NAMESPACE" uninstall "$OPERATOR_RELEASE" >/dev/null 2>&1 || true cleanup_gitea fi # --------------------------------------------------------------------------- # Always remove gitea if its helm release exists (non-destructive path) # gitea and gitlab both claim git.prole.org; they cannot coexist. # --------------------------------------------------------------------------- if helm -n "${GITEA_NAMESPACE:-gitea}" status gitea >/dev/null 2>&1; then warn "Gitea release detected — removing to free git.prole.org for GitLab..." cleanup_gitea fi # --------------------------------------------------------------------------- # Prepare knoe-db # --------------------------------------------------------------------------- setup_knoe_db_for_gitlab # Persist the DB password as a k8s Secret the operator CR can reference resolve_gitlab_db_password kubectl -n "$NAMESPACE" create secret generic gitlab-db-password \ --from-literal=password="$GITLAB_DB_PASSWORD" \ --dry-run=client -o yaml | kubectl apply -f - >/dev/null log "gitlab-db-password secret applied in namespace '$NAMESPACE'." # --------------------------------------------------------------------------- # Install / upgrade the GitLab Operator # --------------------------------------------------------------------------- log "Adding/updating GitLab Operator Helm repo..." helm repo add "$OPERATOR_REPO_NAME" "$OPERATOR_REPO_URL" >/dev/null 2>&1 || true helm repo update >/dev/null 2>&1 log "Installing GitLab Operator (release=${OPERATOR_RELEASE}, ns=${OPERATOR_NAMESPACE})..." # The operator needs cluster-scoped RBAC; it watches all namespaces by default. helm upgrade --install "$OPERATOR_RELEASE" "$OPERATOR_CHART" \ -n "$OPERATOR_NAMESPACE" \ --create-namespace \ --timeout 10m \ --wait \ --set watchNamespace="$NAMESPACE" log "GitLab Operator ready." # --------------------------------------------------------------------------- # Determine node selector block for the GitLab CR # --------------------------------------------------------------------------- NODE_SELECTOR_YAML="" if [[ -n "$NODE_SELECTOR" ]]; then log "Pinning GitLab workloads to node: ${NODE_SELECTOR}" NODE_SELECTOR_YAML="kubernetes.io/hostname: ${NODE_SELECTOR}" fi # --------------------------------------------------------------------------- # Optional chart version pin inside the CR # --------------------------------------------------------------------------- CHART_VERSION_YAML="" if [[ -n "$GITLAB_CHART_VERSION" ]]; then CHART_VERSION_YAML="version: \"${GITLAB_CHART_VERSION}\"" fi # --------------------------------------------------------------------------- # Create the GitLab CR (operator reconciles this into the full deployment) # --------------------------------------------------------------------------- # --------------------------------------------------------------------------- # Apply gitlab-google-oidc secret (OIDC provider config for OmniAuth) # --------------------------------------------------------------------------- if [[ -n "$FRONTDOOR_HOST" ]]; then PLATFORM_DOMAIN="${PLATFORM_DOMAIN:-}" oidc_secret_tmpl="${SCRIPT_DIR}/../deploy/gcp/gke/gitlab-google-oidc-secret.example.yaml" if [[ -f "$oidc_secret_tmpl" ]]; then log "Applying gitlab-google-oidc secret (issuer=https://${FRONTDOOR_HOST}/auth) ..." FRONTDOOR_HOST="$FRONTDOOR_HOST" PLATFORM_DOMAIN="$PLATFORM_DOMAIN" \ envsubst < "$oidc_secret_tmpl" | kubectl apply -n "$NAMESPACE" -f - >/dev/null || \ warn "Could not apply gitlab-google-oidc secret; create it manually from deploy/gcp/gke/gitlab-google-oidc-secret.example.yaml" else warn "gitlab-google-oidc-secret.example.yaml not found; skipping OIDC secret (create it manually)." fi fi log "Applying GitLab CR (domain=${GITLAB_DOMAIN}, db=${GITLAB_DB_NAME}@${DB_HOST})..." kubectl apply -f - </dev/null; then warn "Timed out waiting for GitLab CR condition=Available." warn "The deployment may still be in progress. Check: kubectl -n ${NAMESPACE} describe gitlab ${GITLAB_RELEASE}" fi fi # --------------------------------------------------------------------------- # Kong ingress for git.prole.org -> gitlab-webservice # The GitLab Operator creates the Ingress; this block patches it to Kong class # or creates a fallback Kong-native ingress if the CR-managed one is absent. # --------------------------------------------------------------------------- WEBSERVICE_SVC="${GITLAB_RELEASE}-webservice-default" log "Ensuring Kong ingress for ${GITLAB_DOMAIN} -> ${WEBSERVICE_SVC}:8181 ..." kubectl apply -f - <