mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 10:13:58 +00:00
1185 lines
34 KiB
Bash
Executable File
1185 lines
34 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -u
|
|
|
|
# init_port_forwards.sh
|
|
# Portable-ish (macOS, Ubuntu, Raspberry Pi OS, Alpine) bash init-style script
|
|
# Manages kubectl port-forward daemons defined in port-mapping.cfg.
|
|
|
|
PROG="init_port_forwards"
|
|
|
|
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
# shellcheck disable=SC1090
|
|
source "$SCRIPT_DIR/knoe_cfg.sh"
|
|
|
|
KNOE_HOME="${KNOE_HOME:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)}"
|
|
VERBOSE=0
|
|
PROLE_CFG_OVERRIDE=""
|
|
PORT_MAPPING_FILE_PATH=""
|
|
|
|
PORT_FORWARD_ENABLED="${PORT_FORWARD_ENABLED:-}"
|
|
PORT_FORWARD_K3D_ENABLED="${PORT_FORWARD_K3D_ENABLED:-}"
|
|
PORT_FORWARD_K3S_ENABLED="${PORT_FORWARD_K3S_ENABLED:-}"
|
|
PORT_FORWARD_HOSTS="${PORT_FORWARD_HOSTS:-}"
|
|
PORT_FORWARD_K3D_HOSTS="${PORT_FORWARD_K3D_HOSTS:-}"
|
|
PORT_FORWARD_K3S_HOSTS="${PORT_FORWARD_K3S_HOSTS:-}"
|
|
PORT_MAPPING_FILE="${PORT_MAPPING_FILE:-}"
|
|
PORT_FORWARD_SKIP_VALIDATE="${PORT_FORWARD_SKIP_VALIDATE:-}"
|
|
PORT_FORWARD_SKIP_WAIT="${PORT_FORWARD_SKIP_WAIT:-}"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage:
|
|
$PROG [-v|--verbose] [-f|--force] [-c|--config-file=FILE] <start|stop|restart|status> [component]
|
|
|
|
Options:
|
|
-c, --config-file=FILE Path to a config file to source when generating port-mapping.cfg
|
|
-v, --verbose Verbose output
|
|
-f, --force Force: kill existing processes blocking ports
|
|
|
|
Examples:
|
|
$PROG -c ./k3d.cfg start
|
|
$PROG stop openbao
|
|
$PROG --verbose status
|
|
|
|
Config (k3d.cfg/k3s.cfg/gke.cfg or port-mapping.cfg):
|
|
PORT_FORWARD_K3D_MAPPING_1 = id=dashboard;namespace=kubernetes-dashboard;target=svc/kubernetes-dashboard-kong-proxy;address=127.0.0.1;hostPort=8443;servicePort=443;protocol=TCP;description=Kubernetes Dashboard
|
|
PORT_FORWARD_K3S_MAPPING_1 = id=opentofu;namespace=\${NAMESPACE};target=svc/opentofu;address=0.0.0.0;hostPort=8080;servicePort=8080;protocol=TCP;description=OpenTofu
|
|
Legacy port-mapping.cfg:
|
|
dashboard: local=8000 remote=80 ns=kubernetes-dashboard svc=kubernetes-dashboard-web address=0.0.0.0
|
|
EOF
|
|
}
|
|
|
|
TARGET_ID=""
|
|
FORCE=0
|
|
|
|
log() { printf '%s\n' "$*"; }
|
|
vlog() { [ "$VERBOSE" -eq 1 ] && printf '[verbose] %s\n' "$*" || true; }
|
|
err() { printf '[error] %s\n' "$*" >&2; }
|
|
|
|
have() { command -v "$1" >/dev/null 2>&1; }
|
|
|
|
# ---- Config helpers / derived defaults ----
|
|
cfg_file() {
|
|
local cfg=""
|
|
if declare -F _knoe_cfg_select_cfg_file >/dev/null 2>&1; then
|
|
if [ -n "${KNOE_CONF:-}" ]; then
|
|
cfg="$(_knoe_cfg_select_cfg_file "$KNOE_CONF")"
|
|
if [ -n "$cfg" ]; then
|
|
printf '%s' "$cfg"
|
|
return 0
|
|
fi
|
|
fi
|
|
if [ -n "${KNOE_HOME:-}" ]; then
|
|
cfg="$(_knoe_cfg_select_cfg_file "$KNOE_HOME/conf")"
|
|
if [ -n "$cfg" ]; then
|
|
printf '%s' "$cfg"
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
CFG_FILE="$(cfg_file || true)"
|
|
|
|
cfg_value() {
|
|
local key="$1"
|
|
[ -n "${CFG_FILE:-}" ] || return 0
|
|
awk -F= -v k="$key" '
|
|
/^[[:space:]]*;/ {next}
|
|
/^[[:space:]]*#/ {next}
|
|
/^[[:space:]]*\\[/ {next}
|
|
{
|
|
kk=$1
|
|
sub(/^[[:space:]]+/, "", kk)
|
|
sub(/[[:space:]]+$/, "", kk)
|
|
}
|
|
kk == k {
|
|
v=$2
|
|
sub(/^[[:space:]]+/, "", v)
|
|
sub(/[[:space:]]+$/, "", v)
|
|
val=v
|
|
}
|
|
END { print val }
|
|
' "$CFG_FILE" 2>/dev/null
|
|
}
|
|
|
|
is_truthy() {
|
|
case "${1:-}" in
|
|
1|true|TRUE|True|yes|YES|Yes|on|ON|On) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
current_hostname() {
|
|
local host=""
|
|
if have hostname; then
|
|
host="$(hostname -f 2>/dev/null || hostname 2>/dev/null || true)"
|
|
fi
|
|
if [ -z "$host" ] && [ -n "${HOSTNAME:-}" ]; then
|
|
host="$HOSTNAME"
|
|
fi
|
|
printf '%s' "$host"
|
|
}
|
|
|
|
host_in_allowlist() {
|
|
local list="$1"
|
|
local host="$2"
|
|
local short="${host%%.*}"
|
|
list="${list//,/ }"
|
|
for item in $list; do
|
|
[ -z "$item" ] && continue
|
|
if [ "$item" = "$host" ] || [ "$item" = "$short" ]; then
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
port_forward_allowed_on_host() {
|
|
local allow=""
|
|
case "${KNOE_MODE:-}" in
|
|
k3s) allow="${PORT_FORWARD_K3S_HOSTS:-${PORT_FORWARD_HOSTS:-}}" ;;
|
|
k3d|"") allow="${PORT_FORWARD_K3D_HOSTS:-${PORT_FORWARD_HOSTS:-}}" ;;
|
|
*) allow="${PORT_FORWARD_HOSTS:-}" ;;
|
|
esac
|
|
if [ -z "$allow" ]; then
|
|
return 0
|
|
fi
|
|
local host
|
|
host="$(current_hostname)"
|
|
if [ -z "$host" ]; then
|
|
return 0
|
|
fi
|
|
host_in_allowlist "$allow" "$host"
|
|
}
|
|
|
|
trim() {
|
|
local s="$1"
|
|
s="${s#"${s%%[![:space:]]*}"}"
|
|
s="${s%"${s##*[![:space:]]}"}"
|
|
printf '%s' "$s"
|
|
}
|
|
|
|
resolve_port_mapping_file() {
|
|
if [ -n "${PORT_MAPPING_FILE:-}" ]; then
|
|
printf '%s' "$PORT_MAPPING_FILE"
|
|
return 0
|
|
fi
|
|
if [ -n "${KNOE_CONF:-}" ]; then
|
|
printf '%s/port-mapping.cfg' "$KNOE_CONF"
|
|
return 0
|
|
fi
|
|
if [ -n "${KNOE_HOME:-}" ]; then
|
|
printf '%s/conf/port-mapping.cfg' "$KNOE_HOME"
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
mapping_prefix_for_mode() {
|
|
case "${KNOE_MODE:-}" in
|
|
k3s) printf '%s' "PORT_FORWARD_K3S_MAPPING_" ;;
|
|
k3d|"") printf '%s' "PORT_FORWARD_K3D_MAPPING_" ;;
|
|
*) printf '%s' "PORT_FORWARD_MAPPING_" ;;
|
|
esac
|
|
}
|
|
|
|
collect_cfg_mappings() {
|
|
local prefix="$1"
|
|
local file="$2"
|
|
[ -f "$file" ] || return 1
|
|
awk -v p="$prefix" '
|
|
/^[[:space:]]*[#;]/ {next}
|
|
/^[[:space:]]*\[/ {next}
|
|
{
|
|
line=$0
|
|
pos=index(line, "=")
|
|
if (pos == 0) next
|
|
key=substr(line, 1, pos-1)
|
|
val=substr(line, pos+1)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", val)
|
|
if (key ~ ("^" p "[0-9]+$")) {
|
|
num=substr(key, length(p)+1)+0
|
|
print num "\t" val
|
|
count++
|
|
}
|
|
}
|
|
END { if (count == 0) exit 1 }
|
|
' "$file" | sort -n
|
|
}
|
|
|
|
collect_cfg_mappings_for_mode() {
|
|
local file="$1"
|
|
local prefix
|
|
prefix="$(mapping_prefix_for_mode)"
|
|
if collect_cfg_mappings "$prefix" "$file"; then
|
|
return 0
|
|
fi
|
|
# Fallback to generic mappings if mode-specific ones are absent
|
|
if collect_cfg_mappings "PORT_FORWARD_MAPPING_" "$file"; then
|
|
return 0
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
has_ini_mappings() {
|
|
local file="$1"
|
|
[ -f "$file" ] || return 1
|
|
grep -Eq '^[[:space:]]*PORT_FORWARD_.*_MAPPING_[0-9]+' "$file"
|
|
}
|
|
|
|
parse_cfg_mapping() {
|
|
local line="$1"
|
|
local id="" ns="" target="" address="" hostPort="" servicePort="" protocol="" description=""
|
|
local token key value
|
|
IFS=';' read -ra parts <<<"$line"
|
|
for token in "${parts[@]}"; do
|
|
token="$(trim "$token")"
|
|
[ -z "$token" ] && continue
|
|
key="$(trim "${token%%=*}")"
|
|
value="$(trim "${token#*=}")"
|
|
case "$key" in
|
|
id) id="$value" ;;
|
|
namespace|ns) ns="$value" ;;
|
|
target) target="$value" ;;
|
|
address|addr) address="$value" ;;
|
|
hostPort|host_port|local|host) hostPort="$value" ;;
|
|
servicePort|service_port|remote) servicePort="$value" ;;
|
|
protocol) protocol="$value" ;;
|
|
description|desc) description="$value" ;;
|
|
esac
|
|
done
|
|
|
|
if [ -n "${PROLE_NAMESPACE:-}" ]; then
|
|
ns="${ns//\$\{NAMESPACE\}/$PROLE_NAMESPACE}"
|
|
ns="${ns//\$\{PROLE_NAMESPACE\}/$PROLE_NAMESPACE}"
|
|
fi
|
|
if [ -n "${PF_MONITORING_NAMESPACE:-}" ]; then
|
|
ns="${ns//\$\{MONITORING_NAMESPACE\}/$PF_MONITORING_NAMESPACE}"
|
|
ns="${ns//\$\{PROLE_MONITORING_NAMESPACE\}/$PF_MONITORING_NAMESPACE}"
|
|
fi
|
|
if [ -n "${PF_MANAGEMENT_NAMESPACE:-}" ]; then
|
|
ns="${ns//\$\{MANAGEMENT_NAMESPACE\}/$PF_MANAGEMENT_NAMESPACE}"
|
|
ns="${ns//\$\{PROLE_MANAGEMENT_NAMESPACE\}/$PF_MANAGEMENT_NAMESPACE}"
|
|
fi
|
|
|
|
if [ "$id" = "postgres" ]; then
|
|
if [ -n "${DB_HOST_PORT:-}" ]; then
|
|
hostPort="$DB_HOST_PORT"
|
|
fi
|
|
if [ "$SUPABASE_ENABLED_EFFECTIVE" -eq 1 ]; then
|
|
ns="$SUPABASE_NAMESPACE"
|
|
target="$SUPABASE_DB_TARGET"
|
|
fi
|
|
fi
|
|
|
|
if [ -z "$address" ]; then address="127.0.0.1"; fi
|
|
if [ -z "$protocol" ]; then protocol="TCP"; fi
|
|
|
|
printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
|
|
"$id" "$ns" "$target" "$address" "$hostPort" "$servicePort" "$protocol" "$description"
|
|
}
|
|
|
|
generate_port_mapping_file() {
|
|
local src_cfg="$1"
|
|
local dst_cfg="$2"
|
|
[ -f "$src_cfg" ] || { err "Knoe config file not found: $src_cfg"; return 1; }
|
|
local dst_dir
|
|
dst_dir="$(dirname "$dst_cfg")"
|
|
mkdir -p "$dst_dir" >/dev/null 2>&1 || true
|
|
local tmp="${dst_cfg}.tmp.$$"
|
|
{
|
|
printf '; Port mappings generated by %s\n' "$PROG"
|
|
printf '; Source: %s\n' "$src_cfg"
|
|
awk '
|
|
/^[[:space:]]*[#;]/ {next}
|
|
/^[[:space:]]*\[/ {next}
|
|
{
|
|
line=$0
|
|
pos=index(line, "=")
|
|
if (pos == 0) next
|
|
key=substr(line, 1, pos-1)
|
|
val=substr(line, pos+1)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", key)
|
|
gsub(/^[[:space:]]+|[[:space:]]+$/, "", val)
|
|
if (key ~ /^PORT_FORWARD_.*_MAPPING_[0-9]+$/) {
|
|
print key " = " val
|
|
}
|
|
}
|
|
' "$src_cfg"
|
|
} >"$tmp"
|
|
|
|
if ! grep -q "^PORT_FORWARD_.*_MAPPING_[0-9][0-9]*[[:space:]]*=" "$tmp"; then
|
|
rm -f "$tmp" >/dev/null 2>&1 || true
|
|
err "No port-forward mappings found in $src_cfg"
|
|
return 1
|
|
fi
|
|
|
|
mv "$tmp" "$dst_cfg"
|
|
return 0
|
|
}
|
|
|
|
ensure_port_mapping_file() {
|
|
local src_cfg="$1"
|
|
local dst_cfg="$2"
|
|
if [ -f "$dst_cfg" ]; then
|
|
return 0
|
|
fi
|
|
if [ -z "$src_cfg" ] || [ ! -f "$src_cfg" ]; then
|
|
err "Port mapping file not found: $dst_cfg"
|
|
err "No knoe.cfg available to generate it."
|
|
return 1
|
|
fi
|
|
log "Port mapping file not found; generating $dst_cfg from $src_cfg"
|
|
generate_port_mapping_file "$src_cfg" "$dst_cfg"
|
|
}
|
|
|
|
port_forward_enabled_for_mode() {
|
|
local val=""
|
|
case "${KNOE_MODE:-}" in
|
|
k3s) val="${PORT_FORWARD_K3S_ENABLED:-${PORT_FORWARD_ENABLED:-}}" ;;
|
|
k3d|"") val="${PORT_FORWARD_K3D_ENABLED:-${PORT_FORWARD_ENABLED:-}}" ;;
|
|
*) val="${PORT_FORWARD_ENABLED:-}" ;;
|
|
esac
|
|
if [ -z "$val" ]; then
|
|
return 0
|
|
fi
|
|
is_truthy "$val"
|
|
}
|
|
|
|
port_in_use() {
|
|
local port="$1"
|
|
if have lsof; then
|
|
lsof -nP -iTCP:"$port" -sTCP:LISTEN >/dev/null 2>&1 && return 0 || return 1
|
|
fi
|
|
if have ss; then
|
|
ss -lnt 2>/dev/null | awk '{print $4}' | grep -E "[:.]$port$" >/dev/null 2>&1 && return 0 || return 1
|
|
fi
|
|
if have netstat; then
|
|
netstat -an 2>/dev/null | grep -E "[.:]$port[[:space:]]" | grep -qi listen && return 0 || return 1
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
find_available_port() {
|
|
local base="$1"
|
|
local max="${2:-50}"
|
|
local port="$base"
|
|
local i=0
|
|
while [ "$i" -lt "$max" ]; do
|
|
if ! port_in_use "$port"; then
|
|
printf '%s' "$port"
|
|
return 0
|
|
fi
|
|
port=$((port + 1))
|
|
i=$((i + 1))
|
|
done
|
|
printf '%s' "$base"
|
|
}
|
|
|
|
cfg_set_default() {
|
|
local key="$1"
|
|
local val
|
|
val="$(cfg_value "$key")"
|
|
if [ -n "$val" ]; then
|
|
if [ -z "${!key:-}" ] || [ -n "${PROLE_CFG_OVERRIDE:-}" ]; then
|
|
printf -v "$key" '%s' "$val"
|
|
export "$key"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
init_cfg_context() {
|
|
if [ -n "${CFG_FILE:-}" ] && [ -f "$CFG_FILE" ]; then
|
|
cfg_set_default "NAMESPACE"
|
|
cfg_set_default "MONITORING_NAMESPACE"
|
|
cfg_set_default "MANAGEMENT_NAMESPACE"
|
|
cfg_set_default "PROLE_MONITORING_NAMESPACE"
|
|
cfg_set_default "PROLE_MANAGEMENT_NAMESPACE"
|
|
cfg_set_default "SUPABASE_ENABLED"
|
|
cfg_set_default "SUPABASE_NAMESPACE"
|
|
cfg_set_default "SUPABASE_DB_SERVICE"
|
|
cfg_set_default "DB_HOST_PORT"
|
|
cfg_set_default "KNOE_DB_ALT_PORT"
|
|
fi
|
|
|
|
CFG_NAMESPACE="$(cfg_value "NAMESPACE")"
|
|
CFG_MONITORING_NAMESPACE="$(cfg_value "MONITORING_NAMESPACE")"
|
|
CFG_MANAGEMENT_NAMESPACE="$(cfg_value "MANAGEMENT_NAMESPACE")"
|
|
CFG_PROLE_MONITORING_NAMESPACE="$(cfg_value "PROLE_MONITORING_NAMESPACE")"
|
|
CFG_PROLE_MANAGEMENT_NAMESPACE="$(cfg_value "PROLE_MANAGEMENT_NAMESPACE")"
|
|
|
|
PF_MONITORING_NAMESPACE="${MONITORING_NAMESPACE:-${PROLE_MONITORING_NAMESPACE:-${CFG_MONITORING_NAMESPACE:-${CFG_PROLE_MONITORING_NAMESPACE:-${CFG_NAMESPACE:-}}}}}"
|
|
PF_MANAGEMENT_NAMESPACE="${MANAGEMENT_NAMESPACE:-${PROLE_MANAGEMENT_NAMESPACE:-${CFG_MANAGEMENT_NAMESPACE:-${CFG_PROLE_MANAGEMENT_NAMESPACE:-${CFG_NAMESPACE:-}}}}}"
|
|
|
|
if [ -z "${PF_MONITORING_NAMESPACE:-}" ] && [ -n "${NAMESPACE:-}" ]; then
|
|
PF_MONITORING_NAMESPACE="$NAMESPACE"
|
|
fi
|
|
if [ -z "${PF_MANAGEMENT_NAMESPACE:-}" ] && [ -n "${NAMESPACE:-}" ]; then
|
|
PF_MANAGEMENT_NAMESPACE="$NAMESPACE"
|
|
fi
|
|
|
|
SUPABASE_NAMESPACE="${SUPABASE_NAMESPACE:-supabase}"
|
|
SUPABASE_DB_SERVICE="${SUPABASE_DB_SERVICE:-db}"
|
|
SUPABASE_DB_TARGET="${SUPABASE_DB_TARGET:-svc/$SUPABASE_DB_SERVICE}"
|
|
|
|
SUPABASE_ENABLED_EFFECTIVE=0
|
|
if is_truthy "${SUPABASE_ENABLED:-}"; then
|
|
SUPABASE_ENABLED_EFFECTIVE=1
|
|
elif is_truthy "${init_cluster_supabase_enabled:-}"; then
|
|
SUPABASE_ENABLED_EFFECTIVE=1
|
|
fi
|
|
|
|
KNOE_DB_ALT_PORT_BASE="${KNOE_DB_ALT_PORT_BASE:-15432}"
|
|
KNOE_DB_ALT_PORT_EFFECTIVE=""
|
|
if [ "$SUPABASE_ENABLED_EFFECTIVE" -eq 1 ]; then
|
|
if [ -n "${KNOE_DB_ALT_PORT:-}" ]; then
|
|
KNOE_DB_ALT_PORT_EFFECTIVE="$KNOE_DB_ALT_PORT"
|
|
else
|
|
KNOE_DB_ALT_PORT_EFFECTIVE="$(find_available_port "$KNOE_DB_ALT_PORT_BASE")"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# Choose a writable state dir for pid/log files:
|
|
# - Prefer XDG_RUNTIME_DIR if set and writable
|
|
# - Else /var/run if writable (rare without root)
|
|
# - Else ~/.local/state
|
|
# - Else /tmp
|
|
state_dir() {
|
|
if [ -n "${XDG_RUNTIME_DIR:-}" ] && [ -w "${XDG_RUNTIME_DIR:-}" ]; then
|
|
printf '%s/%s' "$XDG_RUNTIME_DIR" "$PROG"
|
|
return
|
|
fi
|
|
if [ -d "/var/run" ] && [ -w "/var/run" ]; then
|
|
printf '/var/run/%s' "$PROG"
|
|
return
|
|
fi
|
|
if [ -n "${HOME:-}" ]; then
|
|
mkdir -p "$HOME/.local/state" >/dev/null 2>&1 || true
|
|
if [ -d "$HOME/.local/state" ] && [ -w "$HOME/.local/state" ]; then
|
|
printf '%s/.local/state/%s' "$HOME" "$PROG"
|
|
return
|
|
fi
|
|
fi
|
|
printf '/tmp/%s' "$PROG"
|
|
}
|
|
|
|
STATE_DIR="$(state_dir)"
|
|
PID_DIR="$STATE_DIR/pids"
|
|
LOG_DIR="$STATE_DIR/logs"
|
|
|
|
ensure_dirs() {
|
|
mkdir -p "$PID_DIR" "$LOG_DIR" 2>/dev/null || true
|
|
if [ ! -d "$PID_DIR" ] || [ ! -d "$LOG_DIR" ]; then
|
|
err "Unable to create state directories under: $STATE_DIR"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Use kubectl for actions; kubecolor is used only for prettier status output.
|
|
KUBECTL="kubectl"
|
|
detect_kubectl() {
|
|
if have kubectl; then
|
|
KUBECTL="kubectl"
|
|
else
|
|
err "kubectl not found in PATH"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# Basic environment validation
|
|
validate_env() {
|
|
detect_kubectl
|
|
ensure_dirs
|
|
|
|
if is_truthy "${PORT_FORWARD_SKIP_VALIDATE:-}"; then
|
|
return 0
|
|
fi
|
|
|
|
if ! "$KUBECTL" version --client >/dev/null 2>&1; then
|
|
err "kubectl seems broken or not executable"
|
|
exit 1
|
|
fi
|
|
|
|
# Can we reach the cluster?
|
|
if ! "$KUBECTL" cluster-info >/dev/null 2>&1; then
|
|
err "kubectl cannot reach a cluster (check KUBECONFIG/context)"
|
|
err "Try: kubectl config get-contexts && kubectl config use-context <ctx>"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
ensure_supabase_ports() {
|
|
[ "$SUPABASE_ENABLED_EFFECTIVE" -eq 1 ] || return 0
|
|
|
|
local wire_script="$SCRIPT_DIR/init_supabase_ports.sh"
|
|
if [ ! -x "$wire_script" ]; then
|
|
warn "Supabase port wiring script not found/executable: $wire_script"
|
|
return 0
|
|
fi
|
|
|
|
log "Ensuring Supabase DB wiring (db -> knoe-db-rw, supabase-postgres -> 15432) ..."
|
|
SUPABASE_NAMESPACE="$SUPABASE_NAMESPACE" \
|
|
"$wire_script" -n "$NAMESPACE" --supabase-namespace "$SUPABASE_NAMESPACE"
|
|
}
|
|
|
|
# ---- Preflight: Docker + k3d awareness ----
|
|
# Return 0 when Docker CLI can talk to a running daemon.
|
|
docker_is_running() {
|
|
if ! have docker; then
|
|
return 1
|
|
fi
|
|
docker info >/dev/null 2>&1
|
|
}
|
|
|
|
docker_port_owner() {
|
|
local port="$1"
|
|
have docker || return 1
|
|
docker ps --format '{{.ID}}\t{{.Names}}\t{{.Ports}}' 2>/dev/null | \
|
|
awk -v p=":"$port"->" 'index($0, p) {print $1 "\t" $2; exit 0}'
|
|
}
|
|
|
|
should_skip_mapping() {
|
|
local id="$1" hostPort="$2"
|
|
case "$id" in
|
|
registry|openbao)
|
|
local owner
|
|
owner="$(docker_port_owner "$hostPort")"
|
|
if [ -n "$owner" ]; then
|
|
log "Skipping $id port-forward; docker is already publishing port $hostPort ($owner)."
|
|
return 0
|
|
fi
|
|
;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
# Hard-fail with clean guidance when Docker is not up (used for start/restart).
|
|
ensure_docker_running() {
|
|
if docker_is_running; then
|
|
return 0
|
|
fi
|
|
if ! have docker; then
|
|
err "Docker CLI not found. Please install Docker Desktop or docker CLI."
|
|
else
|
|
err "Docker is not running. Start Docker Desktop and wait until it is ready."
|
|
fi
|
|
err "Tip (macOS): open -a Docker"
|
|
err "Then re-run: $PROG start"
|
|
exit 3
|
|
}
|
|
|
|
# Detect k3d cluster name from env or current kubectl context.
|
|
# - If K3D_CLUSTER is set, use it.
|
|
# - Else, if current-context starts with 'k3d-', strip prefix to get name.
|
|
detect_k3d_context() {
|
|
if [ -n "${K3D_CLUSTER:-}" ]; then
|
|
printf '%s' "$K3D_CLUSTER"
|
|
return 0
|
|
fi
|
|
local ctx
|
|
ctx="$($KUBECTL config current-context 2>/dev/null || echo)"
|
|
case "$ctx" in
|
|
k3d-*) printf '%s' "${ctx#k3d-}"; return 0 ;;
|
|
*) return 1 ;;
|
|
esac
|
|
}
|
|
|
|
# Return 0 if the given k3d cluster exists and is running.
|
|
k3d_cluster_is_running() {
|
|
local name="$1"
|
|
have k3d || return 1
|
|
# Use json output when available; fall back to grep otherwise
|
|
if k3d cluster list -o json >/dev/null 2>&1; then
|
|
k3d cluster list -o json 2>/dev/null | grep -q '"name"\s*:\s*"'"$name"'"' && \
|
|
k3d cluster list -o json 2>/dev/null | sed -n 's/.*"name"\s*:\s*"\([^"]\+\)".*"serversRunning"\s*:\s*\([0-9]\+\).*/\1 \2/p' | awk -v n="$name" '$1==n {exit ($2>0)?0:1}'
|
|
return $?
|
|
else
|
|
k3d cluster list 2>/dev/null | grep -E "^$name\s" | grep -q running
|
|
return $?
|
|
fi
|
|
}
|
|
|
|
# If current context indicates k3d, ensure the cluster is up; otherwise no-op.
|
|
ensure_k3d_ready_if_applicable() {
|
|
local k3d_name
|
|
if ! k3d_name="$(detect_k3d_context)"; then
|
|
return 0
|
|
fi
|
|
if ! have k3d; then
|
|
err "k3d is not installed but kubectl context suggests k3d (context=$("$KUBECTL" config current-context))."
|
|
err "Install k3d: brew install k3d (macOS)"
|
|
err "Or switch context: kubectl config use-context <non-k3d-context>"
|
|
exit 4
|
|
fi
|
|
if ! k3d_cluster_is_running "$k3d_name"; then
|
|
err "k3d cluster '$k3d_name' is not running."
|
|
err "Start it: k3d cluster start $k3d_name"
|
|
err "Then re-run: $PROG start"
|
|
exit 4
|
|
fi
|
|
}
|
|
|
|
is_local_mode() {
|
|
case "${KNOE_MODE:-}" in
|
|
""|k3d) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
pid_file_for() { printf '%s/%s.pid' "$PID_DIR" "$1"; }
|
|
log_file_for() { printf '%s/%s.log' "$LOG_DIR" "$1"; }
|
|
|
|
wait_for_service_endpoints() {
|
|
local ns="$1" svc="$2"
|
|
local timeout="${PORT_FORWARD_WAIT_TIMEOUT:-120}"
|
|
local interval="${PORT_FORWARD_WAIT_INTERVAL:-3}"
|
|
local start
|
|
start=$(date +%s)
|
|
|
|
while true; do
|
|
if "$KUBECTL" -n "$ns" get endpoints "$svc" -o jsonpath='{.subsets[*].addresses[*].ip}' 2>/dev/null | grep -q .; then
|
|
return 0
|
|
fi
|
|
if (( $(date +%s) - start > timeout )); then
|
|
err "WARN: Endpoints for service '$svc' in namespace '$ns' not ready after ${timeout}s; continuing."
|
|
return 1
|
|
fi
|
|
sleep "$interval"
|
|
done
|
|
}
|
|
|
|
is_pid_running() {
|
|
# Return 0 if pid exists and running, else 1
|
|
# kill -0 is portable.
|
|
local pid="$1"
|
|
[ -n "$pid" ] && kill -0 "$pid" >/dev/null 2>&1
|
|
}
|
|
|
|
is_matching_port_forward() {
|
|
local pid="$1" target="$2" hostPort="$3" servicePort="$4"
|
|
local cmd
|
|
cmd="$(ps -p "$pid" -o command= 2>/dev/null || true)"
|
|
if [ -z "$cmd" ]; then
|
|
return 1
|
|
fi
|
|
case "$cmd" in
|
|
*port-forward*"$target"*"$hostPort:$servicePort"*) return 0 ;;
|
|
esac
|
|
return 1
|
|
}
|
|
|
|
read_pid() {
|
|
local pf="$1"
|
|
[ -f "$pf" ] || return 1
|
|
# shellcheck disable=SC2162
|
|
read pid <"$pf" || return 1
|
|
printf '%s' "$pid"
|
|
}
|
|
|
|
write_pid() {
|
|
local pf="$1" pid="$2"
|
|
printf '%s\n' "$pid" >"$pf"
|
|
}
|
|
|
|
remove_pidfile() {
|
|
local pf="$1"
|
|
rm -f "$pf" >/dev/null 2>&1 || true
|
|
}
|
|
|
|
handle_mapping_parsed() {
|
|
local callback="$1"
|
|
local parsed="$2"
|
|
local id ns target address hostPort servicePort protocol description
|
|
IFS=$'\t' read -r id ns target address hostPort servicePort protocol description <<<"$parsed"
|
|
|
|
if [ -z "$id" ] || [ -z "$ns" ] || [ -z "$target" ] || [ -z "$hostPort" ] || [ -z "$servicePort" ]; then
|
|
err "Invalid mapping (missing required fields): $parsed"
|
|
exit 1
|
|
fi
|
|
|
|
if [ -n "$TARGET_ID" ] && [ "$id" != "$TARGET_ID" ]; then
|
|
return 0
|
|
fi
|
|
|
|
vlog "mapping: id=$id ns=$ns target=$target address=$address hostPort=$hostPort servicePort=$servicePort protocol=$protocol"
|
|
"$callback" "$id" "$ns" "$target" "$address" "$hostPort" "$servicePort" "$protocol" "$description"
|
|
return 0
|
|
}
|
|
|
|
foreach_mapping_cfg() {
|
|
local callback="$1"
|
|
local file="${2:-$PORT_MAPPING_FILE_PATH}"
|
|
local mappings line parsed count=0
|
|
[ -n "$file" ] || return 1
|
|
if ! mappings="$(collect_cfg_mappings_for_mode "$file")"; then
|
|
return 1
|
|
fi
|
|
while IFS= read -r line; do
|
|
[ -z "$line" ] && continue
|
|
parsed="$(parse_cfg_mapping "${line#*$'\t'}")"
|
|
if handle_mapping_parsed "$callback" "$parsed"; then
|
|
count=$((count + 1))
|
|
fi
|
|
done <<<"$mappings"
|
|
[ "$count" -gt 0 ] || return 1
|
|
return 0
|
|
}
|
|
|
|
foreach_mapping_legacy() {
|
|
local callback="$1"
|
|
local file="${2:-$PORT_MAPPING_FILE_PATH}"
|
|
local line id rest token key value
|
|
local hostPort servicePort ns target address protocol description
|
|
local count=0
|
|
[ -f "$file" ] || return 1
|
|
while IFS= read -r line; do
|
|
line="$(trim "$line")"
|
|
[ -z "$line" ] && continue
|
|
case "$line" in \#*|\;*) continue ;; esac
|
|
id="${line%%:*}"
|
|
rest="${line#*:}"
|
|
if [ "$id" = "$line" ]; then
|
|
continue
|
|
fi
|
|
id="$(trim "$id")"
|
|
rest="$(trim "$rest")"
|
|
hostPort="" servicePort="" ns="" target="" address="" protocol="" description=""
|
|
for token in $rest; do
|
|
key="$(trim "${token%%=*}")"
|
|
value="$(trim "${token#*=}")"
|
|
case "$key" in
|
|
local|host|hostPort|host_port) hostPort="$value" ;;
|
|
remote|servicePort|service_port) servicePort="$value" ;;
|
|
ns|namespace) ns="$value" ;;
|
|
svc|service) target="svc/$value" ;;
|
|
target) target="$value" ;;
|
|
address|addr) address="$value" ;;
|
|
protocol|proto) protocol="$value" ;;
|
|
desc|description) description="$value" ;;
|
|
esac
|
|
done
|
|
|
|
if [ -z "$address" ]; then address="127.0.0.1"; fi
|
|
if [ -z "$protocol" ]; then protocol="TCP"; fi
|
|
|
|
line="id=$id;namespace=$ns;target=$target;address=$address;hostPort=$hostPort;servicePort=$servicePort;protocol=$protocol;description=$description"
|
|
parsed="$(parse_cfg_mapping "$line")"
|
|
if handle_mapping_parsed "$callback" "$parsed"; then
|
|
count=$((count + 1))
|
|
fi
|
|
done <"$file"
|
|
[ "$count" -gt 0 ] || return 1
|
|
return 0
|
|
}
|
|
|
|
foreach_mapping() {
|
|
local callback="$1"
|
|
if [ -n "${CFG_FILE:-}" ] && [ -f "$CFG_FILE" ]; then
|
|
if foreach_mapping_cfg "$callback" "$CFG_FILE"; then
|
|
return 0
|
|
fi
|
|
fi
|
|
if [ -n "${PORT_MAPPING_FILE_PATH:-}" ] && [ -f "$PORT_MAPPING_FILE_PATH" ]; then
|
|
if has_ini_mappings "$PORT_MAPPING_FILE_PATH"; then
|
|
if foreach_mapping_cfg "$callback" "$PORT_MAPPING_FILE_PATH"; then
|
|
return 0
|
|
fi
|
|
else
|
|
if foreach_mapping_legacy "$callback" "$PORT_MAPPING_FILE_PATH"; then
|
|
return 0
|
|
fi
|
|
fi
|
|
fi
|
|
return 1
|
|
}
|
|
|
|
build_port_forward_cmd() {
|
|
# echo a command string
|
|
local ns="$1" target="$2" address="$3" hostPort="$4" servicePort="$5"
|
|
# kubectl port-forward -n <ns> --address <addr> <target> <local>:<remote>
|
|
printf '%s port-forward -n %s --address %s %s %s:%s' \
|
|
"$KUBECTL" "$ns" "$address" "$target" "$hostPort" "$servicePort"
|
|
}
|
|
|
|
start_port_forward() {
|
|
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
|
|
local pidfile logfile cmd pid
|
|
|
|
if should_skip_mapping "$id" "$hostPort"; then
|
|
stop_port_forward "$id" "$ns" "$target" "$address" "$hostPort" "$servicePort" "$protocol" "$description"
|
|
return 0
|
|
fi
|
|
|
|
# Aggressively stop existing processes before starting to avoid port conflicts
|
|
stop_port_forward "$id" "$ns" "$target" "$address" "$hostPort" "$servicePort" "$protocol" "$description"
|
|
|
|
# If target is a service, wait briefly for endpoints to be ready to avoid immediate port-forward failures.
|
|
if [[ "$target" == svc/* ]] && ! is_truthy "${PORT_FORWARD_SKIP_WAIT:-}"; then
|
|
wait_for_service_endpoints "$ns" "${target#svc/}" || true
|
|
fi
|
|
|
|
pidfile="$(pid_file_for "$id")"
|
|
logfile="$(log_file_for "$id")"
|
|
cmd="$(build_port_forward_cmd "$ns" "$target" "$address" "$hostPort" "$servicePort")"
|
|
|
|
log "Starting: $id $address:$hostPort -> $target:$servicePort ($ns) ${description:-}"
|
|
vlog "Command: $cmd"
|
|
|
|
# Start in background, keep output in log.
|
|
# nohup is available on macOS/Linux; redirect stdin from /dev/null to detach.
|
|
nohup sh -c "$cmd" >>"$logfile" 2>&1 </dev/null &
|
|
pid="$!"
|
|
write_pid "$pidfile" "$pid"
|
|
|
|
# Quick verification
|
|
sleep 0.2
|
|
if is_pid_running "$pid"; then
|
|
vlog "$id started (pid=$pid, log=$logfile)"
|
|
return 0
|
|
else
|
|
err "$id failed to start (see log: $logfile)"
|
|
remove_pidfile "$pidfile"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
stop_port_forward() {
|
|
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
|
|
local pidfile pid
|
|
|
|
pidfile="$(pid_file_for "$id")"
|
|
pid=""
|
|
if [ -f "$pidfile" ]; then
|
|
pid="$(read_pid "$pidfile" || true)"
|
|
fi
|
|
|
|
if [ -n "${pid:-}" ] && is_pid_running "$pid"; then
|
|
log "Stopping: $id (pid=$pid)"
|
|
kill "$pid" >/dev/null 2>&1 || true
|
|
|
|
# wait a moment, then SIGKILL if needed
|
|
local i
|
|
for i in 1 2 3 4 5; do
|
|
if ! is_pid_running "$pid"; then break; fi
|
|
sleep 0.2
|
|
done
|
|
if is_pid_running "$pid"; then
|
|
err "$id did not stop gracefully; sending SIGKILL"
|
|
kill -9 "$pid" >/dev/null 2>&1 || true
|
|
fi
|
|
else
|
|
if [ -f "$pidfile" ]; then
|
|
vlog "$id stale pidfile (pid=$pid not running)"
|
|
fi
|
|
fi
|
|
remove_pidfile "$pidfile"
|
|
|
|
# Aggressively remove any other matching kubectl port-forward processes
|
|
# Search for processes that match: kubectl port-forward -n <ns> ... <target> <hostPort>:<servicePort>
|
|
local extra_pids
|
|
extra_pids=$(ps -ef | grep "port-forward" | grep "\-n" | grep "$ns" | grep "$target" | grep "$hostPort:$servicePort" | grep -v grep | awk '{print $2}')
|
|
|
|
for epid in $extra_pids; do
|
|
if [ "$epid" != "$pid" ]; then
|
|
log "Cleaning up orphan process for $id (pid=$epid)"
|
|
kill -9 "$epid" >/dev/null 2>&1 || true
|
|
fi
|
|
done
|
|
}
|
|
|
|
status_one() {
|
|
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
|
|
local pidfile pid
|
|
|
|
pidfile="$(pid_file_for "$id")"
|
|
pid=""
|
|
if [ -f "$pidfile" ]; then
|
|
pid="$(read_pid "$pidfile" || true)"
|
|
fi
|
|
|
|
if [ -n "${pid:-}" ] && is_pid_running "$pid"; then
|
|
printf 'RUNNING %-12s pid=%-7s %s:%s -> %s:%s ns=%s %s\n' \
|
|
"$id" "$pid" "$address" "$hostPort" "$target" "$servicePort" "$ns" "${description:-}"
|
|
# ps details (portable flags vary; use a conservative format)
|
|
ps -p "$pid" -o pid=,ppid=,etime=,command= 2>/dev/null | sed 's/^/ /' || true
|
|
else
|
|
printf 'STOPPED %-12s (no live pid) %s:%s -> %s:%s ns=%s %s\n' \
|
|
"$id" "$address" "$hostPort" "$target" "$servicePort" "$ns" "${description:-}"
|
|
fi
|
|
}
|
|
|
|
scan_for_collisions() {
|
|
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
|
|
|
|
if should_skip_mapping "$id" "$hostPort"; then
|
|
return 0
|
|
fi
|
|
|
|
if port_in_use "$hostPort"; then
|
|
# find which process is using it
|
|
local pid_info=""
|
|
if have lsof; then
|
|
pid_info=$(lsof -nP -iTCP:"$hostPort" -sTCP:LISTEN -t 2>/dev/null | head -n 1)
|
|
fi
|
|
|
|
if [ -n "$pid_info" ]; then
|
|
if is_matching_port_forward "$pid_info" "$target" "$hostPort" "$servicePort"; then
|
|
vlog "Port $hostPort already forwarded by kubectl for $id (pid=$pid_info)."
|
|
return 0
|
|
fi
|
|
# Check if this PID is already managed by us
|
|
local pidfile pid_managed
|
|
pidfile="$(pid_file_for "$id")"
|
|
pid_managed="$(read_pid "$pidfile" 2>/dev/null || true)"
|
|
|
|
if [ "$pid_info" = "$pid_managed" ]; then
|
|
vlog "Port $hostPort is in use by our own process ($id, pid=$pid_info). This is fine for start/restart."
|
|
return 0
|
|
fi
|
|
|
|
local proc_details
|
|
proc_details=$(ps -p "$pid_info" -o pid=,command= 2>/dev/null | sed 's/[[:space:]]\+/ /g' || echo "$pid_info")
|
|
|
|
if [ "$FORCE" -eq 1 ]; then
|
|
log "Port $hostPort is in use by: $proc_details"
|
|
log "Force enabled. Killing process $pid_info..."
|
|
if ! kill -9 "$pid_info" 2>/dev/null; then
|
|
err "Failed to kill process $pid_info. Permission denied?"
|
|
exit 1
|
|
fi
|
|
sleep 0.5
|
|
else
|
|
err "Port collision detected: Port $hostPort is already in use by another process."
|
|
err "Process details: $proc_details"
|
|
err "Use -f or --force to kill the offending process, or stop it manually."
|
|
exit 1
|
|
fi
|
|
else
|
|
# Port in use but we can't find PID (maybe another user's process)
|
|
err "Port collision detected: Port $hostPort is in use, but could not determine PID (check with sudo lsof -i :$hostPort)."
|
|
exit 1
|
|
fi
|
|
fi
|
|
}
|
|
|
|
do_start() {
|
|
if ! port_forward_enabled_for_mode; then
|
|
log "Port forwards disabled for mode '${KNOE_MODE:-auto}'."
|
|
return 0
|
|
fi
|
|
if ! port_forward_allowed_on_host; then
|
|
log "Port forwards disabled on this host ($(current_hostname)); not listed in PORT_FORWARD_*_HOSTS."
|
|
return 0
|
|
fi
|
|
validate_env
|
|
if is_local_mode; then
|
|
# Preflight: require Docker daemon and k3d (if applicable)
|
|
ensure_docker_running
|
|
ensure_k3d_ready_if_applicable
|
|
fi
|
|
ensure_supabase_ports
|
|
|
|
vlog "Scanning for port collisions..."
|
|
if ! foreach_mapping scan_for_collisions; then
|
|
err "No port-forward mappings found. Check ${CFG_FILE:-knoe.cfg} or ${PORT_MAPPING_FILE_PATH:-port-mapping.cfg}."
|
|
exit 1
|
|
fi
|
|
|
|
if ! foreach_mapping start_port_forward; then
|
|
err "No port-forward mappings started. Check ${CFG_FILE:-knoe.cfg} or ${PORT_MAPPING_FILE_PATH:-port-mapping.cfg}."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
do_stop() {
|
|
# stop doesn't require cluster access, but it does need state dirs
|
|
ensure_dirs
|
|
if ! foreach_mapping stop_port_forward; then
|
|
log "No port-forward mappings found to stop."
|
|
fi
|
|
}
|
|
|
|
do_restart() {
|
|
if ! port_forward_enabled_for_mode; then
|
|
log "Port forwards disabled for mode '${KNOE_MODE:-auto}'."
|
|
return 0
|
|
fi
|
|
if ! port_forward_allowed_on_host; then
|
|
log "Port forwards disabled on this host ($(current_hostname)); not listed in PORT_FORWARD_*_HOSTS."
|
|
return 0
|
|
fi
|
|
validate_env
|
|
if is_local_mode; then
|
|
# Preflight: require Docker daemon and k3d (if applicable)
|
|
ensure_docker_running
|
|
ensure_k3d_ready_if_applicable
|
|
fi
|
|
ensure_supabase_ports
|
|
|
|
vlog "Scanning for port collisions (excluding our own)..."
|
|
# During restart, we'll stop them first anyway, but let's be safe.
|
|
if ! foreach_mapping stop_port_forward; then
|
|
err "No port-forward mappings found. Check ${CFG_FILE:-knoe.cfg} or ${PORT_MAPPING_FILE_PATH:-port-mapping.cfg}."
|
|
exit 1
|
|
fi
|
|
if ! foreach_mapping scan_for_collisions; then
|
|
err "No port-forward mappings found. Check ${CFG_FILE:-knoe.cfg} or ${PORT_MAPPING_FILE_PATH:-port-mapping.cfg}."
|
|
exit 1
|
|
fi
|
|
if ! foreach_mapping start_port_forward; then
|
|
err "No port-forward mappings started. Check ${CFG_FILE:-knoe.cfg} or ${PORT_MAPPING_FILE_PATH:-port-mapping.cfg}."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
do_status() {
|
|
if ! port_forward_allowed_on_host; then
|
|
log "Port forwards disabled on this host ($(current_hostname)); not listed in PORT_FORWARD_*_HOSTS."
|
|
return 0
|
|
fi
|
|
validate_env
|
|
# Non-fatal awareness messages
|
|
if docker_is_running; then
|
|
log "[OK] Docker daemon is running"
|
|
else
|
|
if have docker; then
|
|
log "[WARN] Docker is not running"
|
|
else
|
|
log "[WARN] Docker CLI not found"
|
|
fi
|
|
fi
|
|
|
|
local _k3d_name
|
|
if _k3d_name="$(detect_k3d_context)"; then
|
|
if have k3d; then
|
|
if k3d_cluster_is_running "$_k3d_name"; then
|
|
log "[OK] k3d cluster '$_k3d_name' is running"
|
|
else
|
|
log "[WARN] k3d cluster '$_k3d_name' is not running"
|
|
fi
|
|
else
|
|
log "[WARN] k3d not installed but context suggests k3d (cluster='$_k3d_name')"
|
|
fi
|
|
fi
|
|
|
|
log "== Context / Cluster =="
|
|
"$KUBECTL" config current-context 2>/dev/null | sed 's/^/ context: /' || true
|
|
"$KUBECTL" cluster-info 2>/dev/null | sed 's/^/ /' || true
|
|
log ""
|
|
|
|
log "== Port-forward processes =="
|
|
if ! foreach_mapping status_one; then
|
|
log " (no port-forward mappings found)"
|
|
fi
|
|
log ""
|
|
|
|
log "== Quick k3d awareness checks (best-effort) =="
|
|
# If user is on k3d, current-context often includes k3d-... but not guaranteed.
|
|
# Show nodes + a few namespaces/services related to mappings (best effort).
|
|
"$KUBECTL" get nodes -o wide 2>/dev/null | sed 's/^/ /' || true
|
|
log ""
|
|
"$KUBECTL" get ns 2>/dev/null | sed 's/^/ /' || true
|
|
log ""
|
|
|
|
# For each mapping, try to show target existence
|
|
log "== Target existence (best-effort) =="
|
|
_target_check() {
|
|
local id="$1" ns="$2" target="$3" address="$4" hostPort="$5" servicePort="$6" protocol="$7" description="$8"
|
|
# kubectl get <target> -n <ns>
|
|
# If target is like "svc/name", "deploy/name", etc.
|
|
if "$KUBECTL" get -n "$ns" "$target" >/dev/null 2>&1; then
|
|
printf 'OK %-12s %s (ns=%s)\n' "$id" "$target" "$ns"
|
|
else
|
|
printf 'MISSING %-12s %s (ns=%s)\n' "$id" "$target" "$ns"
|
|
fi
|
|
}
|
|
foreach_mapping _target_check
|
|
}
|
|
|
|
# ---- arg parsing ----
|
|
ACTION=""
|
|
|
|
while [ $# -gt 0 ]; do
|
|
case "$1" in
|
|
start|stop|restart|status)
|
|
if [ -z "$ACTION" ]; then
|
|
ACTION="$1"
|
|
else
|
|
TARGET_ID="$1"
|
|
fi
|
|
shift
|
|
;;
|
|
-m|--mode)
|
|
shift
|
|
knoe_set_mode "${1:-}"
|
|
shift
|
|
;;
|
|
-m=*|--mode=*)
|
|
knoe_set_mode "${1#*=}"
|
|
shift
|
|
;;
|
|
-v|--verbose)
|
|
VERBOSE=1
|
|
shift
|
|
;;
|
|
-f|--force)
|
|
FORCE=1
|
|
shift
|
|
;;
|
|
-c)
|
|
shift
|
|
[ $# -gt 0 ] || { err "-c requires a file path"; usage; exit 2; }
|
|
PROLE_CFG_OVERRIDE="$1"
|
|
shift
|
|
;;
|
|
--config-file=*)
|
|
PROLE_CFG_OVERRIDE="${1#*=}"
|
|
shift
|
|
;;
|
|
-h|--help)
|
|
usage
|
|
exit 0
|
|
;;
|
|
*)
|
|
if [ -z "$ACTION" ]; then
|
|
err "Unknown arg: $1"
|
|
usage
|
|
exit 2
|
|
fi
|
|
TARGET_ID="$1"
|
|
shift
|
|
;;
|
|
esac
|
|
done
|
|
|
|
if [ -n "$PROLE_CFG_OVERRIDE" ]; then
|
|
CFG_FILE="$PROLE_CFG_OVERRIDE"
|
|
if [ -f "$CFG_FILE" ]; then
|
|
KNOE_CONF="$(cd "$(dirname "$CFG_FILE")" && pwd)"
|
|
export KNOE_CONF
|
|
fi
|
|
fi
|
|
if [ -z "${CFG_FILE:-}" ]; then
|
|
CFG_FILE="$(cfg_file || true)"
|
|
fi
|
|
|
|
[ -n "$ACTION" ] || { usage; exit 2; }
|
|
|
|
PORT_MAPPING_FILE_PATH="$(resolve_port_mapping_file || true)"
|
|
init_cfg_context
|
|
|
|
if ! ensure_port_mapping_file "$CFG_FILE" "$PORT_MAPPING_FILE_PATH"; then
|
|
exit 1
|
|
fi
|
|
|
|
case "$ACTION" in
|
|
start) do_start ;;
|
|
stop) do_stop ;;
|
|
restart) do_restart ;;
|
|
status) do_status ;;
|
|
esac
|