Files
linux-scripts/gitops-bootstrap.sh
chiefgeek a1a17e81a1 Sync all scripts from website downloads — 352 scripts total
Includes updated JS challenge scripts with Claude-User whitelist,
same-site referer bypass, Blackbox-Exporter allowed bot, and all
new exporters, cheat sheets, and automation scripts.
2026-05-25 03:31:08 +02:00

653 lines
24 KiB
Bash

#!/usr/bin/env bash
#########################################################################################
#### gitops-bootstrap.sh — Bootstrap GitOps on Kubernetes with Flux or ArgoCD ####
#### Install, configure git source, sync applications, and validate deployments ####
#### Requires: bash 4+, kubectl, git, flux CLI or argocd CLI ####
#### ####
#### Author: Phil Connor ####
#### Contact: contact@mylinux.work ####
#### License: MIT ####
#### Version 1.00 ####
#### ####
#### Usage: ####
#### ./gitops-bootstrap.sh --install flux --repo git@github.com:org/infra.git ####
#### ####
#### See --help for all options. ####
#########################################################################################
set -euo pipefail
VERSION="1.00"
# --- ANSI color variables (pre-initialized) ---
RED=""
GREEN=""
YELLOW=""
BLUE=""
CYAN=""
BOLD=""
DIM=""
RESET=""
# --- Defaults ---
RUN_MODE=""
GITOPS_TOOL="${GITOPS_TOOL:-flux}"
GIT_REPO="${GITOPS_REPO:-}"
GIT_BRANCH="${GITOPS_BRANCH:-main}"
GIT_PATH="${GITOPS_PATH:-./clusters/default}"
NAMESPACE="${GITOPS_NAMESPACE:-}"
KUBECONFIG_FILE="${KUBECONFIG:-}"
KUBE_CTX="${KUBE_CONTEXT:-}"
CONFIRM_YES=false
VERBOSE="${VERBOSE:-false}"
COLOR="${COLOR:-auto}"
# --- State ---
readonly SCRIPT_NAME="${0##*/}"
START_TIME=$(date +%s)
# --- Source name used for flux commands ---
SOURCE_NAME="main"
KUSTOMIZATION_NAME="default"
APP_NAME=""
# --- Color setup ---
setup_colors() {
if [[ "$COLOR" == "never" ]]; then
RED="" GREEN="" YELLOW="" BLUE="" CYAN="" BOLD="" DIM="" RESET=""
return
fi
if [[ "$COLOR" == "always" ]] || [[ -t 1 ]]; then
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
BOLD='\033[1m'
DIM='\033[2m'
RESET='\033[0m'
fi
}
# --- Logging ---
log() { printf "%b\n" "${GREEN}${RESET} $*"; }
warn() { printf "%b\n" "${YELLOW}${RESET} $*" >&2; }
err() { printf "%b\n" "${RED}${RESET} $*" >&2; }
verbose() { [[ "$VERBOSE" == "true" ]] && printf "%b\n" "${DIM}$*${RESET}" >&2; return 0; }
die() { err "$*"; exit 1; }
section_header() {
printf "\n%b━━━ %s ━━━%b\n" "${BOLD}${BLUE}" "$1" "${RESET}"
}
field() {
printf " %-22s %s\n" "$1:" "$2"
}
field_color() {
printf " %-22s %b%s%b\n" "$1:" "$2" "$3" "${RESET}"
}
# --- Resolve namespace default based on tool ---
resolve_namespace() {
if [[ -z "$NAMESPACE" ]]; then
if [[ "$GITOPS_TOOL" == "argocd" ]]; then
NAMESPACE="argocd"
else
NAMESPACE="flux-system"
fi
fi
}
# --- kubectl wrapper ---
kubectl_cmd() {
local -a args=("kubectl")
[[ -n "$KUBECONFIG_FILE" ]] && args+=("--kubeconfig" "$KUBECONFIG_FILE")
[[ -n "$KUBE_CTX" ]] && args+=("--context" "$KUBE_CTX")
"${args[@]}" "$@"
}
# --- Dependency checks ---
require_kubectl() {
command -v kubectl >/dev/null 2>&1 || die "kubectl is required but not found in PATH"
}
require_flux() {
command -v flux >/dev/null 2>&1 || die "flux CLI is required but not found in PATH"
}
require_argocd() {
command -v argocd >/dev/null 2>&1 || die "argocd CLI is required but not found in PATH"
}
require_git() {
command -v git >/dev/null 2>&1 || die "git is required but not found in PATH"
}
# --- Confirm prompt ---
confirm_action() {
local prompt="${1:-Continue?}"
if [[ "$CONFIRM_YES" == "true" ]]; then
return 0
fi
printf "%s [y/N] " "$prompt"
read -r answer
[[ "$answer" =~ ^[Yy]$ ]] || die "Aborted"
}
# --- Wait for pods ready in namespace ---
wait_for_pods() {
local ns="$1"
local timeout="${2:-120}"
log "Waiting for pods in namespace ${CYAN}${ns}${RESET} (timeout ${timeout}s)"
local deadline=$(($(date +%s) + timeout))
while true; do
local not_ready
not_ready=$(kubectl_cmd get pods -n "$ns" --no-headers 2>/dev/null \
| grep -cvE 'Running|Completed|Succeeded' || true)
if [[ "$not_ready" -eq 0 ]]; then
local total
total=$(kubectl_cmd get pods -n "$ns" --no-headers 2>/dev/null | wc -l)
if [[ "$total" -gt 0 ]]; then
log "All ${total} pod(s) ready in ${ns}"
return 0
fi
fi
if [[ $(date +%s) -ge $deadline ]]; then
warn "Timeout waiting for pods in ${ns}"
kubectl_cmd get pods -n "$ns" --no-headers 2>/dev/null || true
return 1
fi
sleep 5
done
}
# ─────────────────────────────────────────────────────────────────────
# Install
# ─────────────────────────────────────────────────────────────────────
do_install_flux() {
section_header "Installing Flux"
require_kubectl
require_flux
log "Running pre-flight checks"
verbose "flux check --pre"
flux check --pre || die "Flux pre-flight checks failed"
log "Installing Flux components into namespace ${CYAN}${NAMESPACE}${RESET}"
verbose "flux install --namespace=${NAMESPACE}"
flux install --namespace="$NAMESPACE"
wait_for_pods "$NAMESPACE"
if [[ -n "$GIT_REPO" ]]; then
log "Configuring GitRepository source"
verbose "flux create source git ${SOURCE_NAME} --url=${GIT_REPO} --branch=${GIT_BRANCH} --namespace=${NAMESPACE}"
flux create source git "$SOURCE_NAME" \
--url="$GIT_REPO" \
--branch="$GIT_BRANCH" \
--namespace="$NAMESPACE"
log "Creating Kustomization"
verbose "flux create kustomization ${KUSTOMIZATION_NAME} --source=${SOURCE_NAME} --path=${GIT_PATH} --namespace=${NAMESPACE} --prune=true"
flux create kustomization "$KUSTOMIZATION_NAME" \
--source="$SOURCE_NAME" \
--path="$GIT_PATH" \
--namespace="$NAMESPACE" \
--prune=true
fi
section_header "Flux Installation Summary"
field "Namespace" "$NAMESPACE"
field "Git repository" "${GIT_REPO:-not configured}"
field "Branch" "$GIT_BRANCH"
field "Path" "$GIT_PATH"
log "Flux installation complete"
}
do_install_argocd() {
section_header "Installing Argo CD"
require_kubectl
local manifests_url="https://raw.githubusercontent.com/argoproj/argo-cd/stable/manifests/install.yaml"
log "Creating namespace ${CYAN}${NAMESPACE}${RESET}"
kubectl_cmd create namespace "$NAMESPACE" --dry-run=client -o yaml \
| kubectl_cmd apply -f -
log "Applying Argo CD manifests"
verbose "kubectl apply -n ${NAMESPACE} -f ${manifests_url}"
kubectl_cmd apply -n "$NAMESPACE" -f "$manifests_url"
wait_for_pods "$NAMESPACE"
section_header "Argo CD Installation Summary"
field "Namespace" "$NAMESPACE"
field "Manifests" "$manifests_url"
printf "\n"
log "Retrieve initial admin password:"
printf " %bkubectl -n %s get secret argocd-initial-admin-secret -o jsonpath='{.data.password}' | base64 -d%b\n" \
"${CYAN}" "$NAMESPACE" "${RESET}"
log "Argo CD installation complete"
}
do_install() {
case "$GITOPS_TOOL" in
flux) do_install_flux ;;
argocd) do_install_argocd ;;
*) die "Unknown GitOps tool: ${GITOPS_TOOL}. Use 'flux' or 'argocd'." ;;
esac
}
# ─────────────────────────────────────────────────────────────────────
# Status
# ─────────────────────────────────────────────────────────────────────
do_status_flux() {
section_header "Flux Status"
require_kubectl
require_flux
log "Sources"
flux get sources all --namespace="$NAMESPACE" 2>/dev/null || warn "No sources found"
printf "\n"
log "Kustomizations"
flux get kustomizations --namespace="$NAMESPACE" 2>/dev/null || warn "No kustomizations found"
printf "\n"
log "Helm releases"
flux get helmreleases --all-namespaces 2>/dev/null || verbose "No helm releases found"
}
do_status_argocd() {
section_header "Argo CD Status"
require_kubectl
log "Applications"
kubectl_cmd get applications -n "$NAMESPACE" -o wide 2>/dev/null || warn "No applications found"
printf "\n"
log "App Projects"
kubectl_cmd get appprojects -n "$NAMESPACE" -o wide 2>/dev/null || verbose "No app projects found"
}
do_status() {
case "$GITOPS_TOOL" in
flux) do_status_flux ;;
argocd) do_status_argocd ;;
*) die "Unknown GitOps tool: ${GITOPS_TOOL}" ;;
esac
section_header "Pod Status (${NAMESPACE})"
kubectl_cmd get pods -n "$NAMESPACE" -o wide 2>/dev/null || warn "No pods found"
}
# ─────────────────────────────────────────────────────────────────────
# Add Source
# ─────────────────────────────────────────────────────────────────────
do_add_source_flux() {
section_header "Adding Git Source (Flux)"
require_flux
[[ -z "$GIT_REPO" ]] && die "--repo is required to add a source"
log "Creating GitRepository source ${CYAN}${SOURCE_NAME}${RESET}"
verbose "flux create source git ${SOURCE_NAME} --url=${GIT_REPO} --branch=${GIT_BRANCH} --namespace=${NAMESPACE}"
flux create source git "$SOURCE_NAME" \
--url="$GIT_REPO" \
--branch="$GIT_BRANCH" \
--namespace="$NAMESPACE"
log "Source added successfully"
flux get sources git --namespace="$NAMESPACE"
}
do_add_source_argocd() {
section_header "Adding Git Source (Argo CD)"
require_argocd
[[ -z "$GIT_REPO" ]] && die "--repo is required to add a source"
log "Adding repository ${CYAN}${GIT_REPO}${RESET}"
verbose "argocd repo add ${GIT_REPO}"
argocd repo add "$GIT_REPO" || die "Failed to add repository"
log "Repository added successfully"
}
do_add_source() {
case "$GITOPS_TOOL" in
flux) do_add_source_flux ;;
argocd) do_add_source_argocd ;;
*) die "Unknown GitOps tool: ${GITOPS_TOOL}" ;;
esac
}
# ─────────────────────────────────────────────────────────────────────
# Sync / Reconcile
# ─────────────────────────────────────────────────────────────────────
do_sync_flux() {
section_header "Reconciling (Flux)"
require_flux
log "Reconciling source git/${SOURCE_NAME}"
verbose "flux reconcile source git ${SOURCE_NAME} --namespace=${NAMESPACE}"
flux reconcile source git "$SOURCE_NAME" --namespace="$NAMESPACE"
log "Reconciling kustomization ${KUSTOMIZATION_NAME}"
verbose "flux reconcile kustomization ${KUSTOMIZATION_NAME} --namespace=${NAMESPACE}"
flux reconcile kustomization "$KUSTOMIZATION_NAME" --namespace="$NAMESPACE"
log "Reconciliation triggered"
}
do_sync_argocd() {
section_header "Syncing (Argo CD)"
require_argocd
if [[ -n "$APP_NAME" ]]; then
log "Syncing application ${CYAN}${APP_NAME}${RESET}"
verbose "argocd app sync ${APP_NAME}"
argocd app sync "$APP_NAME"
else
log "Syncing all applications"
local apps
apps=$(kubectl_cmd get applications -n "$NAMESPACE" -o jsonpath='{range .items[*]}{.metadata.name}{"\n"}{end}' 2>/dev/null || true)
if [[ -z "$apps" ]]; then
warn "No applications found to sync"
return
fi
while IFS= read -r app; do
[[ -z "$app" ]] && continue
log "Syncing ${app}"
argocd app sync "$app" || warn "Failed to sync ${app}"
done <<< "$apps"
fi
log "Sync triggered"
}
do_sync() {
case "$GITOPS_TOOL" in
flux) do_sync_flux ;;
argocd) do_sync_argocd ;;
*) die "Unknown GitOps tool: ${GITOPS_TOOL}" ;;
esac
}
# ─────────────────────────────────────────────────────────────────────
# Validate (pre-flight)
# ─────────────────────────────────────────────────────────────────────
do_validate() {
section_header "Pre-flight Validation"
require_kubectl
local checks_passed=0
local checks_failed=0
# Check kubectl connectivity
log "Checking kubectl connectivity"
if kubectl_cmd cluster-info >/dev/null 2>&1; then
field_color "Cluster access" "${GREEN}" "OK"
checks_passed=$((checks_passed + 1))
else
field_color "Cluster access" "${RED}" "FAILED"
checks_failed=$((checks_failed + 1))
fi
# Check namespace exists
log "Checking namespace ${CYAN}${NAMESPACE}${RESET}"
if kubectl_cmd get namespace "$NAMESPACE" >/dev/null 2>&1; then
field_color "Namespace" "${GREEN}" "${NAMESPACE} exists"
checks_passed=$((checks_passed + 1))
else
field_color "Namespace" "${YELLOW}" "${NAMESPACE} does not exist (will be created)"
checks_passed=$((checks_passed + 1))
fi
# Check CRDs installed
log "Checking CRDs"
if [[ "$GITOPS_TOOL" == "flux" ]]; then
if kubectl_cmd get crd gitrepositories.source.toolkit.fluxcd.io >/dev/null 2>&1; then
field_color "Flux CRDs" "${GREEN}" "installed"
checks_passed=$((checks_passed + 1))
else
field_color "Flux CRDs" "${YELLOW}" "not installed"
checks_passed=$((checks_passed + 1))
fi
elif [[ "$GITOPS_TOOL" == "argocd" ]]; then
if kubectl_cmd get crd applications.argoproj.io >/dev/null 2>&1; then
field_color "ArgoCD CRDs" "${GREEN}" "installed"
checks_passed=$((checks_passed + 1))
else
field_color "ArgoCD CRDs" "${YELLOW}" "not installed"
checks_passed=$((checks_passed + 1))
fi
fi
# Check git repo accessible
if [[ -n "$GIT_REPO" ]]; then
log "Checking git repository accessibility"
require_git
if git ls-remote "$GIT_REPO" HEAD >/dev/null 2>&1; then
field_color "Git repository" "${GREEN}" "accessible"
checks_passed=$((checks_passed + 1))
else
field_color "Git repository" "${RED}" "not accessible"
checks_failed=$((checks_failed + 1))
fi
else
verbose "No git repository specified, skipping connectivity check"
fi
# Check tool CLI available
log "Checking CLI tools"
if [[ "$GITOPS_TOOL" == "flux" ]]; then
if command -v flux >/dev/null 2>&1; then
local flux_ver
flux_ver=$(flux version --client 2>/dev/null | head -1 || echo "unknown")
field_color "flux CLI" "${GREEN}" "$flux_ver"
checks_passed=$((checks_passed + 1))
else
field_color "flux CLI" "${RED}" "not found"
checks_failed=$((checks_failed + 1))
fi
elif [[ "$GITOPS_TOOL" == "argocd" ]]; then
if command -v argocd >/dev/null 2>&1; then
local argocd_ver
argocd_ver=$(argocd version --client --short 2>/dev/null || echo "unknown")
field_color "argocd CLI" "${GREEN}" "$argocd_ver"
checks_passed=$((checks_passed + 1))
else
field_color "argocd CLI" "${RED}" "not found"
checks_failed=$((checks_failed + 1))
fi
fi
section_header "Validation Summary"
field_color "Passed" "${GREEN}" "$checks_passed"
if [[ "$checks_failed" -gt 0 ]]; then
field_color "Failed" "${RED}" "$checks_failed"
die "Validation failed with ${checks_failed} error(s)"
else
field_color "Failed" "${GREEN}" "0"
log "All pre-flight checks passed"
fi
}
# ─────────────────────────────────────────────────────────────────────
# Teardown
# ─────────────────────────────────────────────────────────────────────
do_teardown_flux() {
section_header "Tearing Down Flux"
require_flux
confirm_action "Remove Flux from the cluster?"
log "Uninstalling Flux"
verbose "flux uninstall --namespace=${NAMESPACE} --silent"
flux uninstall --namespace="$NAMESPACE" --silent
log "Flux has been removed from the cluster"
}
do_teardown_argocd() {
section_header "Tearing Down Argo CD"
require_kubectl
confirm_action "Remove Argo CD from the cluster (delete namespace ${NAMESPACE})?"
log "Deleting namespace ${CYAN}${NAMESPACE}${RESET}"
kubectl_cmd delete namespace "$NAMESPACE" --wait=true
log "Argo CD has been removed from the cluster"
}
do_teardown() {
case "$GITOPS_TOOL" in
flux) do_teardown_flux ;;
argocd) do_teardown_argocd ;;
*) die "Unknown GitOps tool: ${GITOPS_TOOL}" ;;
esac
}
# ─────────────────────────────────────────────────────────────────────
# Help
# ─────────────────────────────────────────────────────────────────────
show_help() {
cat <<EOF
${BOLD}GitOps Bootstrap v${VERSION}${RESET}
Bootstrap GitOps (Flux or Argo CD) on a Kubernetes cluster.
${BOLD}MODES:${RESET}
--install TOOL Install GitOps tool on the cluster (flux or argocd)
--status Show GitOps deployment status
--add-source Add a git repository source
--sync Trigger reconciliation / sync
--validate Run pre-flight checks
--teardown Remove GitOps from the cluster
${BOLD}OPTIONS:${RESET}
--tool TOOL GitOps tool: flux (default) or argocd
--repo URL Git repository URL
--branch BRANCH Git branch (default: main)
--path PATH Path within the repo (default: ./clusters/default)
--namespace NS GitOps namespace (default: flux-system or argocd)
--source-name NAME Name for the git source (default: main)
--kustomization NAME Name for the kustomization (default: default)
--app NAME Application name (for argocd sync)
--kubeconfig FILE Path to kubeconfig file
--context CTX Kubernetes context name
--yes Skip confirmation prompts
--verbose Enable verbose output
--no-color Disable colored output
--help Show this help message
${BOLD}ENVIRONMENT VARIABLES:${RESET}
GITOPS_TOOL GitOps tool (flux or argocd)
GITOPS_REPO Git repository URL
GITOPS_BRANCH Git branch
GITOPS_PATH Path within the repo
GITOPS_NAMESPACE GitOps namespace
KUBECONFIG Path to kubeconfig file
KUBE_CONTEXT Kubernetes context name
VERBOSE Enable verbose output (true/false)
COLOR Color mode (auto, always, never)
${BOLD}EXAMPLES:${RESET}
${SCRIPT_NAME} --install flux --repo git@github.com:org/infra.git
${SCRIPT_NAME} --install argocd
${SCRIPT_NAME} --status --tool argocd
${SCRIPT_NAME} --add-source --repo git@github.com:org/infra.git
${SCRIPT_NAME} --sync --tool flux
${SCRIPT_NAME} --validate --repo git@github.com:org/infra.git
${SCRIPT_NAME} --teardown --tool flux --yes
EOF
exit 0
}
# ─────────────────────────────────────────────────────────────────────
# Parse arguments
# ─────────────────────────────────────────────────────────────────────
parse_args() {
while [[ $# -gt 0 ]]; do
case "$1" in
--install)
RUN_MODE="install"
if [[ "${2:-}" == "flux" || "${2:-}" == "argocd" ]]; then
GITOPS_TOOL="$2"; shift
fi
shift
;;
--status) RUN_MODE="status"; shift ;;
--add-source) RUN_MODE="add-source"; shift ;;
--sync) RUN_MODE="sync"; shift ;;
--validate) RUN_MODE="validate"; shift ;;
--teardown) RUN_MODE="teardown"; shift ;;
--tool) GITOPS_TOOL="${2:-}"; shift 2 || die "--tool requires a value" ;;
--repo) GIT_REPO="${2:-}"; shift 2 || die "--repo requires a URL" ;;
--branch) GIT_BRANCH="${2:-}"; shift 2 || die "--branch requires a value" ;;
--path) GIT_PATH="${2:-}"; shift 2 || die "--path requires a value" ;;
--namespace) NAMESPACE="${2:-}"; shift 2 || die "--namespace requires a value" ;;
--source-name) SOURCE_NAME="${2:-}"; shift 2 || die "--source-name requires a value" ;;
--kustomization) KUSTOMIZATION_NAME="${2:-}"; shift 2 || die "--kustomization requires a value" ;;
--app) APP_NAME="${2:-}"; shift 2 || die "--app requires a name" ;;
--kubeconfig) KUBECONFIG_FILE="${2:-}"; shift 2 || die "--kubeconfig requires a file" ;;
--context) KUBE_CTX="${2:-}"; shift 2 || die "--context requires a name" ;;
--yes) CONFIRM_YES=true; shift ;;
--verbose) VERBOSE="true"; shift ;;
--no-color) COLOR="never"; shift ;;
--help) setup_colors; show_help ;;
*) die "Unknown option: $1" ;;
esac
done
if [[ -z "$RUN_MODE" ]]; then err "No mode specified"; echo ""; show_help; exit 1; fi
case "$GITOPS_TOOL" in
flux|argocd) ;;
*) die "Invalid tool '${GITOPS_TOOL}'. Must be 'flux' or 'argocd'." ;;
esac
}
# ─────────────────────────────────────────────────────────────────────
# Main
# ─────────────────────────────────────────────────────────────────────
main() {
parse_args "$@"
setup_colors
resolve_namespace
verbose "Script: ${SCRIPT_NAME}"
verbose "Mode: ${RUN_MODE}"
verbose "Tool: ${GITOPS_TOOL}"
verbose "Namespace: ${NAMESPACE}"
[[ -n "$GIT_REPO" ]] && verbose "Repository: ${GIT_REPO}"
[[ -n "$KUBECONFIG_FILE" ]] && verbose "Kubeconfig: ${KUBECONFIG_FILE}"
[[ -n "$KUBE_CTX" ]] && verbose "Context: ${KUBE_CTX}"
case "$RUN_MODE" in
install) do_install ;;
status) do_status ;;
add-source) do_add_source ;;
sync) do_sync ;;
validate) do_validate ;;
teardown) do_teardown ;;
*) die "Unknown mode: ${RUN_MODE}" ;;
esac
local elapsed=$(( $(date +%s) - START_TIME ))
verbose "Completed in ${elapsed}s"
}
main "$@"