mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
Rename env config files from conf/*/prole.cfg to conf/k3d.cfg, conf/k3s.cfg, and conf/gke.cfg. Update shell/Python loaders and etc/deploy scripts to resolve named configs cleanly while keeping legacy fallback behavior. Align k3s Ansible tasks, docs, and regression coverage with the new configuration layout. Co-authored-by: Junie <junie@jetbrains.com>
1159 lines
30 KiB
Bash
Executable File
1159 lines
30 KiB
Bash
Executable File
#!/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 <local|k3d|k8s> 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> Path to config file (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:-}"
|
|
local candidate=""
|
|
|
|
if [[ -z "$cfg_path" ]]; then
|
|
if [[ -n "${PROLE_CONF:-}" ]]; then
|
|
for candidate in \
|
|
"${PROLE_CONF%/}/k3d.cfg" \
|
|
"${PROLE_CONF%/}/k3s.cfg" \
|
|
"${PROLE_CONF%/}/gke.cfg" \
|
|
"${PROLE_CONF%/}/prole.cfg"; do
|
|
if [[ -f "$candidate" ]]; then
|
|
cfg_path="$candidate"
|
|
break
|
|
fi
|
|
done
|
|
elif [[ -f "$PROJECT_ROOT/conf/k3d.cfg" ]]; then
|
|
cfg_path="$PROJECT_ROOT/conf/k3d.cfg"
|
|
elif [[ -f "$PROJECT_ROOT/conf/k3s.cfg" ]]; then
|
|
cfg_path="$PROJECT_ROOT/conf/k3s.cfg"
|
|
elif [[ -f "$PROJECT_ROOT/conf/gke.cfg" ]]; then
|
|
cfg_path="$PROJECT_ROOT/conf/gke.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" <<EOF
|
|
image:
|
|
repository: gitea/gitea
|
|
tag: "${GITEA_IMAGE_TAG}"
|
|
pullPolicy: IfNotPresent
|
|
|
|
service:
|
|
http:
|
|
type: ClusterIP
|
|
port: 3000
|
|
ssh:
|
|
type: ClusterIP
|
|
port: 22
|
|
|
|
ingress:
|
|
enabled: ${ingress_enabled}
|
|
className: ${ingress_class}
|
|
annotations:
|
|
konghq.com/strip-path: "false"
|
|
hosts:
|
|
- host: ${GITEA_DOMAIN}
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
tls: []
|
|
|
|
gitea:
|
|
config:
|
|
server:
|
|
DOMAIN: ${GITEA_DOMAIN}
|
|
SSH_DOMAIN: ${GITEA_SSH_DOMAIN}
|
|
ROOT_URL: "http://${GITEA_DOMAIN}/"
|
|
HTTP_PORT: 3000
|
|
SSH_PORT: 22
|
|
database:
|
|
DB_TYPE: postgres
|
|
HOST: "${KNOE_DB_SERVICE}.${db_ns}.svc.cluster.local:5432"
|
|
NAME: gitea
|
|
USER: gitea
|
|
PASSWD: "${gitea_db_password}"
|
|
SSL_MODE: disable
|
|
session:
|
|
PROVIDER: db
|
|
cache:
|
|
ADAPTER: memory
|
|
queue:
|
|
TYPE: level
|
|
indexer:
|
|
ISSUE_INDEXER_TYPE: bleve
|
|
REPO_INDEXER_ENABLED: "true"
|
|
ldap:
|
|
- name: "prole-authority"
|
|
securityProtocol: unencrypted
|
|
host: "${ldap_host}"
|
|
port: "${AUTHORITY_LDAP_PORT}"
|
|
userSearchBase: "ou=users,${AUTHORITY_BASE_DN}"
|
|
userFilter: "(&(objectClass=inetOrgPerson)(uid=%s))"
|
|
adminFilter: "(memberOf=cn=admins,ou=groups,${AUTHORITY_BASE_DN})"
|
|
emailAttribute: mail
|
|
usernameAttribute: uid
|
|
firstnameAttribute: givenName
|
|
surnameAttribute: sn
|
|
publicSSHKeyAttribute: sshPublicKey
|
|
bindDn: "${AUTHORITY_BIND_DN}"
|
|
bindPassword: "${AUTHORITY_BIND_PASSWORD:-}"
|
|
synchronizeUsers: "true"
|
|
attributesInBind: "false"
|
|
|
|
# Disable the bundled PostgreSQL — we use knoe-db (cloudnative-pg) instead.
|
|
postgresql:
|
|
enabled: false
|
|
|
|
postgresql-ha:
|
|
enabled: false
|
|
|
|
persistence:
|
|
enabled: true
|
|
size: 10Gi
|
|
EOF
|
|
}
|
|
|
|
ensure_gitea_namespace() {
|
|
if ! kubectl get namespace "$GITEA_NAMESPACE" >/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 - <<PY | kubectl replace --raw "/api/v1/namespaces/${GITEA_NAMESPACE}/finalize" -f - >/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" <<EOF
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: gitea-kong
|
|
namespace: ${GITEA_NAMESPACE}
|
|
annotations:
|
|
konghq.com/strip-path: "false"
|
|
konghq.com/protocols: "http,https"
|
|
spec:
|
|
ingressClassName: kong
|
|
rules:
|
|
- host: ${GITEA_DOMAIN}
|
|
http:
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: gitea-http
|
|
port:
|
|
number: 3000
|
|
EOF
|
|
log " Kong ingress manifest written: $out_dir/gitea-kong-ingress.yaml"
|
|
}
|
|
|
|
run_helm() {
|
|
if [[ "$MODE" == "local" ]]; then
|
|
return 1
|
|
fi
|
|
|
|
ensure_helm
|
|
require_cmd kubectl
|
|
kubectl cluster-info >/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" <<EOF
|
|
apiVersion: apps/v1
|
|
kind: Deployment
|
|
metadata:
|
|
name: gitea
|
|
namespace: ${GITEA_NAMESPACE}
|
|
spec:
|
|
replicas: 1
|
|
selector:
|
|
matchLabels:
|
|
app: gitea
|
|
template:
|
|
metadata:
|
|
labels:
|
|
app: gitea
|
|
spec:
|
|
containers:
|
|
- name: gitea
|
|
image: gitea/gitea:${GITEA_IMAGE_TAG}
|
|
ports:
|
|
- name: http
|
|
containerPort: 3000
|
|
- name: ssh
|
|
containerPort: 22
|
|
env:
|
|
- name: GITEA__database__DB_TYPE
|
|
value: postgres
|
|
- name: GITEA__database__HOST
|
|
value: "${KNOE_DB_SERVICE}.${KNOE_DB_NAMESPACE:-knoe-db-a0001}.svc.cluster.local:5432"
|
|
- name: GITEA__database__NAME
|
|
value: gitea
|
|
- name: GITEA__database__USER
|
|
value: gitea
|
|
- name: GITEA__database__PASSWD
|
|
value: "${GITEA_DB_PASSWORD:-}"
|
|
- name: GITEA__server__DOMAIN
|
|
value: "${GITEA_DOMAIN}"
|
|
- name: GITEA__server__ROOT_URL
|
|
value: "http://${GITEA_DOMAIN}/"
|
|
volumeMounts:
|
|
- name: data
|
|
mountPath: /data
|
|
volumes:
|
|
- name: data
|
|
emptyDir: {}
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: gitea-http
|
|
namespace: ${GITEA_NAMESPACE}
|
|
spec:
|
|
selector:
|
|
app: gitea
|
|
ports:
|
|
- name: http
|
|
port: 3000
|
|
targetPort: 3000
|
|
---
|
|
apiVersion: v1
|
|
kind: Service
|
|
metadata:
|
|
name: gitea-ssh
|
|
namespace: ${GITEA_NAMESPACE}
|
|
spec:
|
|
selector:
|
|
app: gitea
|
|
ports:
|
|
- name: ssh
|
|
port: 22
|
|
targetPort: 22
|
|
EOF
|
|
|
|
if [[ "$MODE" == "k3s" ]]; then
|
|
cat >> "$manifest_file" <<EOF
|
|
---
|
|
apiVersion: networking.k8s.io/v1
|
|
kind: Ingress
|
|
metadata:
|
|
name: gitea-kong
|
|
namespace: ${GITEA_NAMESPACE}
|
|
annotations:
|
|
konghq.com/strip-path: "false"
|
|
konghq.com/protocols: "http,https"
|
|
spec:
|
|
ingressClassName: kong
|
|
rules:
|
|
- host: ${GITEA_DOMAIN}
|
|
http:
|
|
paths:
|
|
- path: /
|
|
pathType: Prefix
|
|
backend:
|
|
service:
|
|
name: gitea-http
|
|
port:
|
|
number: 3000
|
|
EOF
|
|
fi
|
|
|
|
log "Applying fallback manifest..."
|
|
kubectl apply -f "$manifest_file"
|
|
rm -f "$manifest_file"
|
|
}
|
|
|
|
run_k3d() {
|
|
# Helm-first path (preferred)
|
|
if run_helm; then
|
|
return 0
|
|
fi
|
|
|
|
log "Prole::Gitea k3d deploy (Kubernetes)"
|
|
log "Repo: $GITEA_DIR"
|
|
|
|
require_cmd kubectl
|
|
ensure_k3d
|
|
ensure_k3d_cluster
|
|
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
|
|
|
|
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 "$@"
|