236 lines
6.3 KiB
Bash
Executable File
236 lines
6.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
|
|
# Defaults
|
|
MODE=""
|
|
WITH_LIVEKIT=false
|
|
CUSTOM_VALUES=""
|
|
NODES=""
|
|
NAMESPACE="loop"
|
|
RELEASE_NAME="loop-stack"
|
|
LIVEKIT_RELEASE_NAME="loop-livekit"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $0 MODE [OPTIONS]
|
|
|
|
Install Loop in an air-gapped environment.
|
|
|
|
Modes:
|
|
helm Deploy to Kubernetes via Helm
|
|
compose Deploy via Docker Compose
|
|
|
|
Options:
|
|
--with-livekit Also install LiveKit server
|
|
--values FILE Additional Helm values file (helm mode only)
|
|
--namespace NS Kubernetes namespace (default: loop)
|
|
--release NAME Helm release name (default: loop-stack)
|
|
--nodes node1,node2,... SSH to these nodes to load images (helm mode only)
|
|
-h, --help Show this help
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
[[ $# -eq 0 ]] && usage
|
|
|
|
MODE="$1"; shift
|
|
|
|
case "$MODE" in
|
|
helm|compose) ;;
|
|
-h|--help) usage ;;
|
|
*) echo "ERROR: Unknown mode '$MODE'. Use 'helm' or 'compose'."; usage ;;
|
|
esac
|
|
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--with-livekit) WITH_LIVEKIT=true; shift ;;
|
|
--values) [[ -z "${2:-}" ]] && { echo "ERROR: --values requires an argument"; exit 1; }; CUSTOM_VALUES="$2"; shift 2 ;;
|
|
--namespace) [[ -z "${2:-}" ]] && { echo "ERROR: --namespace requires an argument"; exit 1; }; NAMESPACE="$2"; shift 2 ;;
|
|
--release) [[ -z "${2:-}" ]] && { echo "ERROR: --release requires an argument"; exit 1; }; RELEASE_NAME="$2"; shift 2 ;;
|
|
--nodes) [[ -z "${2:-}" ]] && { echo "ERROR: --nodes requires an argument"; exit 1; }; NODES="$2"; shift 2 ;;
|
|
-h|--help) usage ;;
|
|
*) echo "Unknown option: $1"; usage ;;
|
|
esac
|
|
done
|
|
|
|
echo "==> Loop Air-Gapped Installer"
|
|
echo " Mode: ${MODE}"
|
|
echo " LiveKit: ${WITH_LIVEKIT}"
|
|
|
|
# ---- Detect container runtime ----
|
|
detect_runtime() {
|
|
if command -v docker &>/dev/null; then
|
|
echo "docker"
|
|
elif command -v ctr &>/dev/null; then
|
|
echo "containerd"
|
|
elif command -v crictl &>/dev/null; then
|
|
echo "crictl"
|
|
else
|
|
echo "none"
|
|
fi
|
|
}
|
|
|
|
# ---- Load images locally ----
|
|
load_images_local() {
|
|
local dir="$1"
|
|
local runtime
|
|
runtime=$(detect_runtime)
|
|
|
|
echo "==> Loading images from ${dir} (runtime: ${runtime})..."
|
|
|
|
for tarfile in "$dir"/*.tar; do
|
|
[[ -f "$tarfile" ]] || continue
|
|
echo " Loading: $(basename "$tarfile")"
|
|
case "$runtime" in
|
|
docker)
|
|
docker load -i "$tarfile"
|
|
;;
|
|
containerd)
|
|
ctr -n k8s.io images import "$tarfile"
|
|
;;
|
|
crictl)
|
|
if command -v ctr &>/dev/null; then
|
|
ctr -n k8s.io images import "$tarfile"
|
|
else
|
|
echo " WARNING: crictl detected but ctr not found. Please load manually:"
|
|
echo " ctr -n k8s.io images import $tarfile"
|
|
fi
|
|
;;
|
|
*)
|
|
echo " ERROR: No container runtime found. Install docker or containerd."
|
|
exit 1
|
|
;;
|
|
esac
|
|
done
|
|
}
|
|
|
|
# ---- Load images on remote nodes via SSH ----
|
|
load_images_remote() {
|
|
local dir="$1"
|
|
local nodes_csv="$2"
|
|
|
|
IFS=',' read -ra NODE_LIST <<< "$nodes_csv"
|
|
|
|
for node in "${NODE_LIST[@]}"; do
|
|
node=$(echo "$node" | xargs)
|
|
echo "==> Loading images on remote node: ${node}"
|
|
|
|
ssh "$node" "mkdir -p /tmp/loop-airgap-images"
|
|
|
|
echo " Copying image tars to ${node}..."
|
|
scp "$dir"/*.tar "${node}:/tmp/loop-airgap-images/"
|
|
|
|
ssh "$node" bash -s <<'REMOTE_SCRIPT'
|
|
set -euo pipefail
|
|
if command -v ctr &>/dev/null; then
|
|
for f in /tmp/loop-airgap-images/*.tar; do
|
|
echo " Loading: $(basename "$f")"
|
|
ctr -n k8s.io images import "$f"
|
|
done
|
|
elif command -v docker &>/dev/null; then
|
|
for f in /tmp/loop-airgap-images/*.tar; do
|
|
echo " Loading: $(basename "$f")"
|
|
docker load -i "$f"
|
|
done
|
|
else
|
|
echo "ERROR: No container runtime found on $(hostname)"
|
|
exit 1
|
|
fi
|
|
rm -rf /tmp/loop-airgap-images
|
|
REMOTE_SCRIPT
|
|
|
|
echo " Done: ${node}"
|
|
done
|
|
}
|
|
|
|
# ==== MAIN INSTALLATION ====
|
|
|
|
# ---- Load core images locally ----
|
|
load_images_local "$SCRIPT_DIR/images"
|
|
|
|
# ---- Load LiveKit images if requested ----
|
|
if [[ "$WITH_LIVEKIT" == "true" ]] && [[ -d "$SCRIPT_DIR/images/livekit" ]]; then
|
|
load_images_local "$SCRIPT_DIR/images/livekit"
|
|
fi
|
|
|
|
# ---- Load images on remote nodes (Helm mode only) ----
|
|
if [[ "$MODE" == "helm" ]] && [[ -n "$NODES" ]]; then
|
|
load_images_remote "$SCRIPT_DIR/images" "$NODES"
|
|
if [[ "$WITH_LIVEKIT" == "true" ]] && [[ -d "$SCRIPT_DIR/images/livekit" ]]; then
|
|
load_images_remote "$SCRIPT_DIR/images/livekit" "$NODES"
|
|
fi
|
|
fi
|
|
|
|
# ==== HELM MODE ====
|
|
if [[ "$MODE" == "helm" ]]; then
|
|
if ! command -v helm &>/dev/null; then
|
|
echo "ERROR: 'helm' is required but not found in PATH"
|
|
exit 1
|
|
fi
|
|
|
|
CHART_DIR="$SCRIPT_DIR/helm/loop-enterprise-stack"
|
|
AIRGAP_VALUES="$CHART_DIR/values-airgap.yaml"
|
|
|
|
echo "==> Installing Loop via Helm..."
|
|
echo " Namespace: ${NAMESPACE}"
|
|
echo " Release: ${RELEASE_NAME}"
|
|
|
|
HELM_CMD="helm upgrade --install ${RELEASE_NAME} ${CHART_DIR}"
|
|
HELM_CMD+=" --namespace ${NAMESPACE} --create-namespace"
|
|
|
|
if [[ -f "$AIRGAP_VALUES" ]]; then
|
|
HELM_CMD+=" -f ${AIRGAP_VALUES}"
|
|
fi
|
|
|
|
if [[ -n "$CUSTOM_VALUES" ]]; then
|
|
HELM_CMD+=" -f ${CUSTOM_VALUES}"
|
|
fi
|
|
|
|
echo " Running: ${HELM_CMD}"
|
|
eval "$HELM_CMD"
|
|
|
|
if [[ "$WITH_LIVEKIT" == "true" ]]; then
|
|
LK_CHART_DIR="$SCRIPT_DIR/helm/loop-livekit-server"
|
|
LK_AIRGAP_VALUES="$LK_CHART_DIR/values-airgap.yaml"
|
|
|
|
echo "==> Installing LiveKit via Helm..."
|
|
LK_CMD="helm upgrade --install ${LIVEKIT_RELEASE_NAME} ${LK_CHART_DIR}"
|
|
LK_CMD+=" --namespace ${NAMESPACE} --create-namespace"
|
|
|
|
if [[ -f "$LK_AIRGAP_VALUES" ]]; then
|
|
LK_CMD+=" -f ${LK_AIRGAP_VALUES}"
|
|
fi
|
|
|
|
echo " Running: ${LK_CMD}"
|
|
eval "$LK_CMD"
|
|
fi
|
|
|
|
echo ""
|
|
echo "==> Helm installation complete!"
|
|
echo " Check status: helm list -n ${NAMESPACE}"
|
|
echo " Check pods: kubectl get pods -n ${NAMESPACE}"
|
|
fi
|
|
|
|
# ==== COMPOSE MODE ====
|
|
if [[ "$MODE" == "compose" ]]; then
|
|
COMPOSE_DIR="$SCRIPT_DIR/docker-compose"
|
|
AIRGAP_OVERRIDE="$COMPOSE_DIR/docker-compose-airgap.override.yml"
|
|
|
|
echo "==> Installing Loop via Docker Compose..."
|
|
|
|
COMPOSE_CMD="docker compose -f ${COMPOSE_DIR}/docker-compose.yml"
|
|
if [[ -f "$AIRGAP_OVERRIDE" ]]; then
|
|
COMPOSE_CMD+=" -f ${AIRGAP_OVERRIDE}"
|
|
fi
|
|
COMPOSE_CMD+=" up -d"
|
|
|
|
echo " Running: ${COMPOSE_CMD}"
|
|
eval "$COMPOSE_CMD"
|
|
|
|
echo ""
|
|
echo "==> Docker Compose installation complete!"
|
|
echo " Check status: docker compose -f ${COMPOSE_DIR}/docker-compose.yml ps"
|
|
fi
|