#!/usr/bin/env bash set -euo pipefail SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" PROJECT_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" if [[ -f "$PROJECT_ROOT/env.sh" ]]; then # shellcheck disable=SC1090 source "$PROJECT_ROOT/env.sh" fi MODE="k3d" HELM_RELEASE="gitea" USE_DEV_HELPERS="false" FOREGROUND="false" FORCE="true" PROLE_CFG_PATH="" HELM_TEMPLATE_ONLY="false" usage() { cat <<'USAGE' gitea/deploy.sh Launches the full Gitea open-source stack locally, following the README/DEVELOPERS guidance (Docker Compose). This is a multi-service deployment (Postgres, Auth, Storage, Realtime, Kong, Studio, etc.) Usage: ./deploy.sh [options] Options: --mode Deployment mode ('local' for Docker Compose, 'k3d' for local k3d, 'k8s' for generic k8s/k3s; default: k3d) --helm-template-only Render Helm manifests to gitea/k8s without applying (for pipelines) --with-dev-helpers Include docker/dev/docker-compose.dev.yml --foreground Run docker compose in the foreground (default: detached, local mode only) -f, --force Reset the gitea namespace before applying manifests (k3d only; default) --no-force Skip namespace reset (k3d only) -c, --config Path to prole.cfg (loads environment defaults) -h, --help Show help Notes: - This script syncs the gitea repo into $DEV_HOME/gitea. - For k3d mode, manifests are applied from $GITEA_K8S_DIR. - Docker image artifacts are cached in $DOCKER_IMPORT_DIR. - steps: 1) use docker/docker-compose.yml 2) copy docker/.env.example -> docker/.env (if missing) 3) run docker compose up USAGE } die() { echo "Error: $*" >&2 exit 1 } warn() { echo "Warning: $*" >&2 } log() { echo "==> $*" } apply_defaults() { DEV_HOME="${DEV_HOME:-$HOME/dev}" DEV_HOME="${DEV_HOME/#\~/$HOME}" GITEA_DIR="$DEV_HOME/gitea" DOCKER_DIR="$GITEA_DIR/docker" COMPOSE_FILE="$DOCKER_DIR/docker-compose.yml" DEV_COMPOSE_FILE="$DOCKER_DIR/dev/docker-compose.dev.yml" ENV_EXAMPLE="$DOCKER_DIR/.env.example" ENV_FILE="$DOCKER_DIR/.env" GITEA_K8S_DIR="${GITEA_K8S_DIR:-$SCRIPT_DIR/k8s}" local default_owner default_owner="$(id -un 2>/dev/null || echo prole)" local data_root="${PROLE_DATA:-/opt/prole/data/${default_owner}}" data_root="${data_root%/}" DOCKER_IMPORT_DIR="${DOCKER_IMPORT_DIR:-$data_root/docker-import}" GITEA_IMAGE_PLATFORM="${GITEA_IMAGE_PLATFORM:-}" GITEA_IMAGE_PLATFORMS="${GITEA_IMAGE_PLATFORMS:-linux/amd64 linux/arm64}" GITEA_IMAGE_TAG="${GITEA_IMAGE_TAG:-1.22.3}" GITEA_DOMAIN="${GITEA_DOMAIN:-git.prole.org}" GITEA_SSH_DOMAIN="${GITEA_SSH_DOMAIN:-git.prole.org}" GITEA_NAMESPACE="${GITEA_NAMESPACE:-gitea}" KNOE_DB_SERVICE="${KNOE_DB_SERVICE:-knoe-db-rw}" KNOE_DB_NAMESPACE="${KNOE_DB_NAMESPACE:-}" AUTHORITY_NAMESPACE="${AUTHORITY_NAMESPACE:-authority}" AUTHORITY_SERVICE="${AUTHORITY_SERVICE:-authority-ldap}" AUTHORITY_LDAP_PORT="${AUTHORITY_LDAP_PORT:-389}" AUTHORITY_BASE_DN="${AUTHORITY_BASE_DN:-dc=prole,dc=org}" AUTHORITY_BIND_DN="${AUTHORITY_BIND_DN:-cn=gitea,ou=services,dc=prole,dc=org}" AUTHORITY_BIND_PASSWORD="${AUTHORITY_BIND_PASSWORD:-}" } load_prole_cfg() { local cfg_loader="$PROJECT_ROOT/etc/prole_cfg.sh" local cfg_path="${PROLE_CFG_PATH:-}" if [[ -z "$cfg_path" ]]; then if [[ -n "${PROLE_CONF:-}" ]]; then cfg_path="${PROLE_CONF%/}/prole.cfg" elif [[ -f "$PROJECT_ROOT/conf/prole.cfg" ]]; then cfg_path="$PROJECT_ROOT/conf/prole.cfg" fi fi if [[ -z "$cfg_path" ]]; then return 0 fi if [[ -n "$cfg_path" ]]; then if [[ -d "$cfg_path" ]]; then cfg_path="$cfg_path/prole.cfg" fi [[ -f "$cfg_path" ]] || die "Config file not found: $cfg_path" PROLE_CONF="$(cd "$(dirname "$cfg_path")" && pwd)" export PROLE_CONF PROLE_CFG_PATH="$cfg_path" fi if [[ -f "$cfg_loader" ]]; then # shellcheck disable=SC1090 source "$cfg_loader" else warn "prole_cfg.sh not found; config defaults may be incomplete." fi } register_gitea_ports() { if command -v prole_register_port_forward >/dev/null 2>&1; then prole_register_port_forward "gitea-http" "$GITEA_NAMESPACE" "svc/gitea-http" "3000" "3000" "0.0.0.0" "TCP" "Gitea Web UI" prole_register_port_forward "gitea-ssh" "$GITEA_NAMESPACE" "svc/gitea-ssh" "2222" "22" "0.0.0.0" "TCP" "Gitea SSH" fi } require_cmd() { command -v "$1" >/dev/null 2>&1 || die "Missing required command: $1" } detect_platform() { case "$(uname -m 2>/dev/null || true)" in arm64|aarch64) echo "linux/arm64" ;; x86_64|amd64) echo "linux/amd64" ;; *) echo "linux/amd64" ;; esac } normalize_platform() { case "${1:-}" in linux/*) echo "$1" ;; arm64|aarch64) echo "linux/arm64" ;; x86_64|amd64) echo "linux/amd64" ;; "") detect_platform ;; *) detect_platform ;; esac } compose_cmd() { if docker compose version >/dev/null 2>&1; then echo "docker compose" return 0 fi if command -v docker-compose >/dev/null 2>&1; then echo "docker-compose" return 0 fi die "Docker Compose not found (expected 'docker compose' or 'docker-compose')" } ensure_k3d() { require_cmd k3d } ensure_helm() { require_cmd helm } list_k3d_clusters() { local json="" if json="$(k3d cluster list -o json 2>/dev/null)"; then if [[ -n "$json" ]]; then if printf '%s' "$json" | python3 - <<'PY' import json import sys raw = sys.stdin.read() if not raw.strip(): sys.exit(1) try: data = json.loads(raw) except Exception: sys.exit(1) items = data.get("items") if isinstance(data, dict) else data if not items: sys.exit(0) for item in items: name = item.get("name") if isinstance(item, dict) else None if name: print(name) PY then return 0 fi fi fi k3d cluster list 2>/dev/null | awk 'NR>1 {print $1}' } cluster_exists() { local name="$1" list_k3d_clusters | awk -v target="$name" '$0 == target {found=1} END {exit found ? 0 : 1}' } ensure_k3d_cluster() { local cluster="${K3D_CLUSTER_NAME:-}" local -a clusters local picked="" mapfile -t clusters < <(list_k3d_clusters || true) if [[ -n "$cluster" ]]; then if ! cluster_exists "$cluster"; then log "Creating k3d cluster '$cluster'..." k3d cluster create "$cluster" >/dev/null fi picked="$cluster" elif [[ ${#clusters[@]} -gt 0 ]]; then for name in "${clusters[@]}"; do if [[ "$name" == "k3s-default" ]]; then picked="$name" break fi done if [[ -z "$picked" ]]; then picked="${clusters[0]}" fi else picked="k3s-default" log "Creating k3d cluster '$picked'..." k3d cluster create "$picked" >/dev/null fi export K3D_CLUSTER_NAME="$picked" log "Using k3d cluster: $K3D_CLUSTER_NAME" k3d cluster start "$K3D_CLUSTER_NAME" >/dev/null 2>&1 || true k3d kubeconfig merge "$K3D_CLUSTER_NAME" --switch-context >/dev/null 2>&1 || true } parse_args() { while [[ $# -gt 0 ]]; do case "$1" in --mode|-m) MODE="${2:-}" shift 2 ;; -c|--config) PROLE_CFG_PATH="${2:-}" [[ -n "$PROLE_CFG_PATH" ]] || die "Missing value for $1" shift 2 ;; --config=*) PROLE_CFG_PATH="${1#*=}" [[ -n "$PROLE_CFG_PATH" ]] || die "Missing value for $1" shift ;; --with-dev-helpers) USE_DEV_HELPERS="true" shift ;; --helm-template-only) HELM_TEMPLATE_ONLY="true" shift ;; --foreground) FOREGROUND="true" shift ;; -f|--force) FORCE="true" shift ;; --no-force) FORCE="false" shift ;; -h|--help) usage exit 0 ;; *) die "Unknown option: $1" ;; esac done } ensure_env_file() { if [[ -f "$ENV_FILE" ]]; then return 0 fi [[ -f "$ENV_EXAMPLE" ]] || die "Missing env example file: $ENV_EXAMPLE" log "Creating docker/.env from docker/.env.example" cp "$ENV_EXAMPLE" "$ENV_FILE" warn "docker/.env contains default secrets. Update them before any production use." } compose_files() { local -a files=("-f" "docker-compose.yml") if [[ "$USE_DEV_HELPERS" == "true" ]]; then [[ -f "$DEV_COMPOSE_FILE" ]] || die "Missing dev helpers compose file: $DEV_COMPOSE_FILE" files+=("-f" "dev/docker-compose.dev.yml") fi echo "${files[@]}" } ensure_repo() { require_cmd git mkdir -p "$DEV_HOME" if [[ -d "$GITEA_DIR/.git" ]]; then log "Updating Gitea repo in $GITEA_DIR" (cd "$GITEA_DIR" && git pull) return 0 fi if [[ -e "$GITEA_DIR" ]]; then die "Path exists but is not a git repo: $GITEA_DIR" fi log "Cloning Gitea repo into $GITEA_DIR" git clone https://github.com/go-gitea/gitea.git "$GITEA_DIR" } run_local() { ensure_repo [[ -f "$COMPOSE_FILE" ]] || die "Missing compose file: $COMPOSE_FILE" require_cmd docker docker info >/dev/null 2>&1 || die "Docker daemon is not running" local compose compose="$(compose_cmd)" log "Prole::Gitea local deploy (Docker Compose)" log "Repo: $GITEA_DIR" log "Namespace: gitea (Compose project name)" ensure_env_file local compose_args=("-f" "docker-compose.yml") if [[ "$USE_DEV_HELPERS" == "true" ]]; then [[ -f "$DEV_COMPOSE_FILE" ]] || die "Missing dev helpers compose file: $DEV_COMPOSE_FILE" compose_args+=("-f" "dev/docker-compose.dev.yml") fi local up_args=("up") if [[ "$FOREGROUND" != "true" ]]; then up_args+=("-d") fi log "Starting Gitea services..." (cd "$DOCKER_DIR" && COMPOSE_PROJECT_NAME="gitea" $compose "${compose_args[@]}" "${up_args[@]}") log "Deployment complete." log "Access Gitea at http://localhost:3000" } image_safe_name() { echo "$1" | sed 's/[\/:@]/_/g' } image_platform_tag() { local img="$1" local platform="$2" local suffix="${platform//\//-}" local name tag if [[ "$img" == *@* ]]; then name="${img%@*}" tag="digest-${suffix}" elif [[ "$img" == *:* ]]; then name="${img%:*}" tag="${img##*:}-${suffix}" else name="$img" tag="latest-${suffix}" fi echo "${name}:${tag}" } ensure_image_artifact() { local img="$1" local platform="$2" local safe_name local tar_path local platform_tag safe_name="$(image_safe_name "$img")" tar_path="${DOCKER_IMPORT_DIR}/${safe_name}.${platform//\//-}.tar" platform_tag="$(image_platform_tag "$img" "$platform")" if [[ -f "$tar_path" ]]; then return 0 fi mkdir -p "$DOCKER_IMPORT_DIR" if docker buildx version >/dev/null 2>&1; then log "Fetching image $img for platform $platform (buildx export)" if ! printf 'FROM %s\n' "$img" \ | docker buildx build --pull --platform "$platform" -t "$platform_tag" \ --output "type=docker,dest=$tar_path" - >/dev/null 2>&1; then die "Failed to export image $img ($platform) via buildx" fi return 0 fi log "Fetching image $img for platform $platform" if ! docker pull --platform "$platform" "$img" >/dev/null 2>&1; then if docker image inspect "$img" >/dev/null 2>&1; then warn "Using local image for $img (pull failed for $platform)" else die "Failed to pull image for $img ($platform)" fi fi docker tag "$img" "$platform_tag" >/dev/null 2>&1 || true log "Saving $img ($platform) to $tar_path" if ! docker save -o "$tar_path" "$platform_tag" >/dev/null 2>&1; then die "Failed to save image $img ($platform) to $tar_path" fi } load_image_for_platform() { local img="$1" local platform="$2" local safe_name local tar_path local platform_tag safe_name="$(image_safe_name "$img")" tar_path="${DOCKER_IMPORT_DIR}/${safe_name}.${platform//\//-}.tar" platform_tag="$(image_platform_tag "$img" "$platform")" if [[ ! -f "$tar_path" ]]; then ensure_image_artifact "$img" "$platform" fi log "Loading $img ($platform) from $tar_path" docker load -i "$tar_path" 2>&1 | sed 's/^/ /' docker tag "$platform_tag" "$img" } # Returns 0 if the image is already present in k3d's containerd store, 1 otherwise. image_in_k3d() { local img="$1" local cluster="${K3D_CLUSTER_NAME:-}" local node="k3d-${cluster}-server-0" if ! docker inspect "$node" >/dev/null 2>&1; then return 1 fi docker exec "$node" crictl images --no-trunc -o json 2>/dev/null \ | python3 - "$img" <<'PY' import json, sys target = sys.argv[1] try: data = json.load(sys.stdin) except Exception: sys.exit(1) for entry in data.get('images', []): for tag in entry.get('repoTags', []): if tag == target: sys.exit(0) sys.exit(1) PY } # Build the list of images already present in k3d's containerd store. list_k3d_images() { local cluster="${K3D_CLUSTER_NAME:-}" local node="k3d-${cluster}-server-0" if ! docker inspect "$node" >/dev/null 2>&1; then return 0 fi docker exec "$node" crictl images --no-trunc -o json 2>/dev/null \ | python3 - <<'PY' import json, sys try: data = json.load(sys.stdin) except Exception: sys.exit(0) for entry in data.get('images', []): for tag in entry.get('repoTags', []): print(tag) PY } write_artifact_manifest() { local images="$1" local list_path="${DOCKER_IMPORT_DIR}/gitea-images.txt" mkdir -p "$DOCKER_IMPORT_DIR" printf "%s\n" $images > "$list_path" local platform for platform in $GITEA_IMAGE_PLATFORMS; do local platform_list="${DOCKER_IMPORT_DIR}/gitea-images-${platform//\//-}.txt" : > "$platform_list" for img in $images; do local safe_name safe_name="$(image_safe_name "$img")" echo "${DOCKER_IMPORT_DIR}/${safe_name}.${platform//\//-}.tar" >> "$platform_list" done done } prefetch_k3d_images() { require_cmd docker docker info >/dev/null 2>&1 || die "Docker daemon is not running" # Gitea core images (from the helm chart / compose) local images images="gitea/gitea:${GITEA_IMAGE_TAG}" # If a docker-compose.yml exists in the gitea repo, prefer its image list if [[ -f "$COMPOSE_FILE" ]]; then local compose compose="$(compose_cmd)" ensure_env_file local files files=($(compose_files)) local discovered discovered=$(cd "$DOCKER_DIR" && $compose "${files[@]}" config --images 2>/dev/null | awk 'NF' | sort -u || true) if [[ -n "$discovered" ]]; then images="$discovered" fi fi log "Gitea images discovered:" printf " - %s\n" $images write_artifact_manifest "$images" log "Image artifact list written to $DOCKER_IMPORT_DIR/gitea-images.txt" local platform for platform in $GITEA_IMAGE_PLATFORMS; do log "Ensuring artifacts for platform '$platform' in $DOCKER_IMPORT_DIR" for img in $images; do ensure_image_artifact "$img" "$platform" done done local deploy_platform deploy_platform="$(normalize_platform "${GITEA_IMAGE_PLATFORM:-}")" log "Loading images for platform '$deploy_platform'" for img in $images; do load_image_for_platform "$img" "$deploy_platform" done # Build a list of images already present in k3d to avoid re-importing them. log "Checking k3d registry for already-imported images..." local k3d_existing k3d_existing="$(list_k3d_images || true)" local -a import_delta=() local -a skipped=() for img in $images; do if printf '%s\n' "$k3d_existing" | grep -qxF "$img"; then skipped+=("$img") else import_delta+=("$img") fi done if [[ ${#skipped[@]} -gt 0 ]]; then log "Already in k3d registry (skipping ${#skipped[@]} image(s)):" printf " [skip] %s\n" "${skipped[@]}" fi if [[ ${#import_delta[@]} -eq 0 ]]; then log "All images already present in k3d registry; nothing to import." return 0 fi log "Importing ${#import_delta[@]} image(s) into k3d cluster '${K3D_CLUSTER_NAME:-}' (delta only)..." local idx=0 for img in "${import_delta[@]}"; do idx=$(( idx + 1 )) log " [${idx}/${#import_delta[@]}] Importing $img into k3d..." if [[ -n "${K3D_CLUSTER_NAME:-}" ]]; then k3d image import "$img" -c "$K3D_CLUSTER_NAME" else k3d image import "$img" fi log " [${idx}/${#import_delta[@]}] Done: $img" done log "k3d image import complete (${#import_delta[@]} imported, ${#skipped[@]} already present)." } resolve_knoe_db_namespace() { if [[ -n "${KNOE_DB_NAMESPACE:-}" ]]; then return 0 fi if [[ -n "${NAMESPACE:-}" ]]; then KNOE_DB_NAMESPACE="$NAMESPACE" return 0 fi if kubectl get namespace prole >/dev/null 2>&1; then KNOE_DB_NAMESPACE="prole" else KNOE_DB_NAMESPACE="default" fi } setup_knoe_db_for_gitea() { log "Ensuring Gitea database and role exist in knoe-db..." local ns="${KNOE_DB_NAMESPACE:-knoe-db-a0001}" local primary primary=$(kubectl -n "$ns" get pods \ -l "cnpg.io/cluster=knoe-db,cnpg.io/instanceRole=primary" \ -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) if [[ -z "$primary" ]]; then primary=$(kubectl -n "$ns" get pods -l "cnpg.io/cluster=knoe-db" \ -o jsonpath='{.items[0].metadata.name}' 2>/dev/null || true) fi if [[ -z "$primary" ]]; then warn "No knoe-db pod found in namespace '$ns'; skipping Gitea DB setup." return 0 fi local pg_password="" if kubectl get secret knoe-db-superuser -n "$ns" >/dev/null 2>&1; then pg_password=$(kubectl get secret knoe-db-superuser -n "$ns" \ -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || true) fi if [[ -z "$pg_password" ]]; then pg_password="${POSTGRES_PASSWORD:-}" fi if [[ -z "$pg_password" ]]; then warn "Cannot resolve postgres password; skipping Gitea DB role setup." return 0 fi local gitea_db_password="${GITEA_DB_PASSWORD:-${pg_password}}" kubectl -n "$ns" exec "$primary" -c postgres -- psql -U postgres -d postgres -c " DO \$\$ BEGIN IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname='gitea') THEN CREATE ROLE gitea LOGIN PASSWORD '${gitea_db_password}'; END IF; END \$\$; " 2>&1 || warn "Could not create gitea role." local db_exists db_exists=$(kubectl -n "$ns" exec "$primary" -c postgres -- psql -U postgres -d postgres -tAc \ "SELECT 1 FROM pg_database WHERE datname = 'gitea';" 2>/dev/null || true) if [[ "$db_exists" != "1" ]]; then kubectl -n "$ns" exec "$primary" -c postgres -- psql -U postgres -d postgres -c \ "CREATE DATABASE gitea OWNER gitea;" 2>&1 || warn "Could not create gitea database." fi log " Gitea database and role ready in knoe-db." } resolve_authority_ldap() { # Resolve the LDAP bind password from the authority pod/secret if not already set if [[ -n "${AUTHORITY_BIND_PASSWORD:-}" ]]; then return 0 fi local ns="${AUTHORITY_NAMESPACE}" if kubectl get secret authority-ldap-bind -n "$ns" >/dev/null 2>&1; then AUTHORITY_BIND_PASSWORD=$(kubectl get secret authority-ldap-bind -n "$ns" \ -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || true) fi if [[ -z "${AUTHORITY_BIND_PASSWORD:-}" ]]; then warn "Could not resolve LDAP bind password from authority-ldap-bind secret; LDAP config may be incomplete." fi } # Build the Helm values for Gitea, incorporating cloudnative-pg and authority LDAP. build_helm_values() { local values_file="$1" local pg_password="${2:-}" local gitea_db_password="${GITEA_DB_PASSWORD:-${pg_password}}" local db_ns="${KNOE_DB_NAMESPACE:-knoe-db-a0001}" resolve_authority_ldap local ldap_host ldap_host="${AUTHORITY_SERVICE}.${AUTHORITY_NAMESPACE}.svc.cluster.local" local ingress_enabled="false" local ingress_class="${GITEA_INGRESS_CLASS:-kong}" if [[ "$MODE" == "k3s" || "$MODE" == "k8s" ]]; then ingress_enabled="true" fi cat > "$values_file" </dev/null 2>&1; then log "Creating '$GITEA_NAMESPACE' namespace..." kubectl create namespace "$GITEA_NAMESPACE" fi } force_reset_gitea_namespace() { if ! kubectl get namespace "$GITEA_NAMESPACE" >/dev/null 2>&1; then ensure_gitea_namespace return fi log "Resetting '$GITEA_NAMESPACE' namespace..." kubectl delete all --all -n "$GITEA_NAMESPACE" --timeout=60s >/dev/null 2>&1 || true kubectl delete namespace "$GITEA_NAMESPACE" --ignore-not-found --wait=false >/dev/null 2>&1 || true if kubectl get namespace "$GITEA_NAMESPACE" >/dev/null 2>&1; then if ! kubectl wait --for=delete "namespace/$GITEA_NAMESPACE" --timeout=120s >/dev/null 2>&1; then warn "Namespace deletion stalled; clearing finalizers" python3 - </dev/null 2>&1 || true import json import subprocess import sys try: raw = subprocess.check_output(["kubectl", "get", "namespace", "${GITEA_NAMESPACE}", "-o", "json"]) if not raw: sys.exit(0) data = json.loads(raw) if "spec" in data: data["spec"]["finalizers"] = [] print(json.dumps(data)) except Exception: sys.exit(0) PY kubectl wait --for=delete "namespace/$GITEA_NAMESPACE" --timeout=60s >/dev/null 2>&1 || true fi fi ensure_gitea_namespace } write_kong_ingress_k3s() { local out_dir="$1" cat > "$out_dir/gitea-kong-ingress.yaml" </dev/null 2>&1 || return 1 if [[ "$FORCE" == "true" ]]; then kubectl delete namespace "$GITEA_NAMESPACE" --ignore-not-found --wait=false >/dev/null 2>&1 || true fi resolve_knoe_db_namespace setup_knoe_db_for_gitea # Resolve postgres password for DB values local pg_password="" local db_ns="${KNOE_DB_NAMESPACE:-knoe-db-a0001}" if kubectl get secret knoe-db-superuser -n "$db_ns" >/dev/null 2>&1; then pg_password=$(kubectl get secret knoe-db-superuser -n "$db_ns" \ -o jsonpath='{.data.password}' 2>/dev/null | base64 -d 2>/dev/null || true) fi if [[ -z "$pg_password" ]]; then pg_password="${POSTGRES_PASSWORD:-}" fi local values_file values_file="$(mktemp /tmp/gitea-values.XXXXXX.yaml)" build_helm_values "$values_file" "$pg_password" # Ensure the helm-gitea chart repo is registered if ! helm repo list 2>/dev/null | grep -q "gitea-charts"; then log "Adding gitea-charts Helm repo..." helm repo add gitea-charts https://dl.gitea.com/charts/ >/dev/null 2>&1 || warn "helm repo add failed" fi helm repo update >/dev/null 2>&1 || true local k8s_dir="$SCRIPT_DIR/k8s" mkdir -p "$k8s_dir" if [[ "$HELM_TEMPLATE_ONLY" == "true" ]]; then log "Rendering Gitea Helm manifests to $k8s_dir (template only)..." helm template "$HELM_RELEASE" gitea-charts/gitea \ --namespace "$GITEA_NAMESPACE" \ -f "$values_file" \ --output-dir "$k8s_dir" >/dev/null if [[ "$MODE" == "k3s" ]]; then write_kong_ingress_k3s "$k8s_dir" fi log "Helm template only; manifests ready in gitea/k8s" rm -f "$values_file" return 0 fi log "Installing Gitea via Helm into namespace '$GITEA_NAMESPACE'..." if helm upgrade --install "$HELM_RELEASE" gitea-charts/gitea \ -n "$GITEA_NAMESPACE" --create-namespace -f "$values_file"; then rm -f "$values_file" # Apply Kong ingress separately for k3s context if [[ "$MODE" == "k3s" ]]; then write_kong_ingress_k3s "$k8s_dir" log "Applying Kong ingress for git.prole.org..." kubectl apply -f "$k8s_dir/gitea-kong-ingress.yaml" fi register_gitea_ports log "Deployment complete via Helm." return 0 else rm -f "$values_file" warn "Helm install failed; falling back to manifest deploy." return 1 fi } run_manifest_fallback() { local manifest_file manifest_file="$(mktemp /tmp/gitea-manifest.XXXXXX.yaml)" cat > "$manifest_file" <> "$manifest_file" </dev/null 2>&1 || die "Kubernetes cluster not reachable" if [[ "$FORCE" == "true" ]]; then force_reset_gitea_namespace else ensure_gitea_namespace fi GITEA_IMAGE_PLATFORM="$(normalize_platform "${GITEA_IMAGE_PLATFORM:-}")" prefetch_k3d_images resolve_knoe_db_namespace setup_knoe_db_for_gitea run_manifest_fallback register_gitea_ports log "Deployment complete." log "Access Gitea via Kong proxy (check ingress/service in '$GITEA_NAMESPACE' namespace)." } run_k8s() { # Helm-first path (preferred) — for k3s use context prole_k3s if [[ "$MODE" == "k3s" ]]; then local ctx="${PROLE_K3S_CONTEXT:-prole_k3s}" if kubectl config get-contexts "$ctx" >/dev/null 2>&1; then log "Switching to k3s context: $ctx" kubectl config use-context "$ctx" >/dev/null 2>&1 || true fi fi if run_helm; then return 0 fi log "Prole::Gitea k8s/k3s deploy (Kubernetes - generic)" log "Repo: $GITEA_DIR" require_cmd kubectl kubectl cluster-info >/dev/null 2>&1 || die "Kubernetes cluster not reachable" if [[ "$FORCE" == "true" ]]; then force_reset_gitea_namespace else ensure_gitea_namespace fi resolve_knoe_db_namespace setup_knoe_db_for_gitea run_manifest_fallback register_gitea_ports log "Deployment complete." log "Access Gitea at http://${GITEA_DOMAIN}/ (via Kong ingress)" } main() { parse_args "$@" load_prole_cfg apply_defaults if [[ -z "$MODE" ]]; then usage exit 1 fi log "Starting Gitea deployment..." log "Mode: $MODE" case "$MODE" in local) run_local ;; k3d) run_k3d ;; k8s|k3s) run_k8s ;; *) die "Unsupported mode: $MODE" ;; esac } main "$@"