#!/bin/sh # ---------- get.nginx-lens.com install.sh ---------- # Установка Control Plane или Agent с https://get.nginx-lens.com. # # Рекомендуется (полный стек UI+infra, если есть Docker Compose v2): # curl -fsSL https://get.nginx-lens.com | sh # curl -fsSL https://get.nginx-lens.com | sh -s -- --compose # # Если HTTPS :443 рвётся (DPI): http:// или https://get.nginx-lens.com:8443 # curl -fsSL http://get.nginx-lens.com | sh # curl -fsSL https://get.nginx-lens.com:8443 | sh # # Только бинарники api+worker (+ infra через docker, если доступен): # curl -fsSL https://get.nginx-lens.com | sh -s -- --binary # # Agent: # curl -fsSL https://get.nginx-lens.com | sh -s -- --agent --api-url https://cp.example:8080 # # Версия: # curl -fsSL https://get.nginx-lens.com | sh -s -- --version v1.6.2 set -eu BASE_URL="${NL_GET_URL:-https://get.nginx-lens.com}" VERSION="${NL_VERSION:-v1.6.2}" MODE="control-plane" INSTALL_KIND="auto" API_URL="" PREFIX="/usr/local/bin" COMPOSE_DIR="${NL_COMPOSE_DIR:-/opt/nginx-lens}" # ---------- require_root ---------- # Скрипт пишет в /usr/local, /opt и systemd. require_root() { if [ "$(id -u)" -ne 0 ]; then echo "run as root (sudo)" >&2 exit 1 fi } # ---------- detect_arch ---------- # linux amd64 / arm64 для имён артефактов на get. detect_arch() { m="$(uname -m)" case "$m" in x86_64|amd64) echo "amd64" ;; aarch64|arm64) echo "arm64" ;; *) echo "unsupported arch: $m" >&2 exit 1 ;; esac } # ---------- download ---------- # curl предпочтительнее; fallback wget. download() { url="$1" dest="$2" if command -v curl >/dev/null 2>&1; then curl -fsSL "$url" -o "$dest" elif command -v wget >/dev/null 2>&1; then wget -qO "$dest" "$url" else echo "curl or wget required" >&2 exit 1 fi } # ---------- verify_sha256 ---------- # Сверяет файл с соседним .sha256 (имя файла внутри). verify_sha256() { file="$1" sum_file="$2" if command -v sha256sum >/dev/null 2>&1; then (cd "$(dirname "$file")" && sha256sum -c "$(basename "$sum_file")") elif command -v shasum >/dev/null 2>&1; then expect="$(awk '{print $1}' "$sum_file")" got="$(shasum -a 256 "$file" | awk '{print $1}')" [ "$expect" = "$got" ] || { echo "sha256 mismatch" >&2; exit 1; } else echo "warn: no sha256 tool, skipping checksum" >&2 fi } # ---------- fetch_artifact ---------- # Скачивает tar.gz + sha256, распаковывает бинарь в PREFIX. fetch_artifact() { art="$1" bin_name="$2" url="${BASE_URL%/}/${VERSION}/${art}" echo "Downloading ${url}" download "$url" "$TMP/$art" download "${url}.sha256" "$TMP/${art}.sha256" verify_sha256 "$TMP/$art" "$TMP/${art}.sha256" tar -xzf "$TMP/$art" -C "$TMP" install -m 755 "$TMP/$bin_name" "$PREFIX/$bin_name" } # ---------- have_compose ---------- # Нужен плагин Docker Compose v2: `docker compose`, не legacy docker-compose. have_compose() { command -v docker >/dev/null 2>&1 || return 1 docker compose version >/dev/null 2>&1 } # ---------- have_docker ---------- # Достаточно для infra-only compose. have_docker() { command -v docker >/dev/null 2>&1 } # ---------- rand_hex ---------- # Секреты для .env / api.env. rand_hex() { n="${1:-32}" if command -v openssl >/dev/null 2>&1; then openssl rand -hex "$n" 2>/dev/null && return 0 fi if [ -r /dev/urandom ]; then od -An -N"$n" -tx1 /dev/urandom 2>/dev/null | tr -d ' \n' | head -c "$((n * 2))" echo return 0 fi echo "changeme$(date +%s)please16chars" } # ---------- pick_http_port ---------- # Если 80 занят — используем 8080 для UI. pick_http_port() { if command -v ss >/dev/null 2>&1; then if ss -ltn 2>/dev/null | awk '{print $4}' | grep -Eq '(:|^)80$'; then echo 8080 return fi elif command -v netstat >/dev/null 2>&1; then if netstat -ltn 2>/dev/null | awk '{print $4}' | grep -Eq '(:|^)\.?80$'; then echo 8080 return fi fi echo 80 } # ---------- write_compose_env ---------- # Создаёт .env с уникальными секретами, если файла ещё нет. write_compose_env() { dest="$1" if [ -f "$dest" ]; then return 0 fi port="$(pick_http_port)" jwt="$(rand_hex 32)" boot="nlx_$(rand_hex 12)" pub="http://localhost" if [ "$port" != "80" ]; then pub="http://localhost:${port}" fi cat >"$dest" <&2 echo "Install: https://docs.docker.com/compose/install/linux/" >&2 echo "Or retry: curl -fsSL ${BASE_URL} | sh -s -- --binary" >&2 exit 1 fi install -d -m 755 "$COMPOSE_DIR" echo "Downloading compose stack → ${COMPOSE_DIR}" download "${BASE_URL%/}/${VERSION}/docker-compose.yml" "$COMPOSE_DIR/docker-compose.yml" download "${BASE_URL%/}/${VERSION}/.env.example" "$COMPOSE_DIR/.env.example" write_compose_env "$COMPOSE_DIR/.env" ( cd "$COMPOSE_DIR" docker compose pull docker compose up -d ) # shellcheck disable=SC1090 . "$COMPOSE_DIR/.env" echo "Control plane (compose) installed (${VERSION})" echo " dir: ${COMPOSE_DIR}" echo " UI: ${NL_PUBLIC_URL:-http://localhost}" echo " bootstrap: NL_BOOTSTRAP_TOKEN in ${COMPOSE_DIR}/.env" echo " status: cd ${COMPOSE_DIR} && docker compose ps" } # ---------- start_infra_compose ---------- # Postgres/Redis/Rabbit для бинарной установки (порты на localhost). start_infra_compose() { if ! have_compose; then echo "warn: no docker compose — start Postgres/Redis/Rabbit yourself (see api.env)" >&2 return 0 fi install -d -m 755 "$COMPOSE_DIR" download "${BASE_URL%/}/${VERSION}/docker-compose.infra.yml" "$COMPOSE_DIR/docker-compose.infra.yml" ( cd "$COMPOSE_DIR" docker compose -f docker-compose.infra.yml pull docker compose -f docker-compose.infra.yml up -d ) echo "Infra up: ${COMPOSE_DIR}/docker-compose.infra.yml (5432/6379/5672)" } # ---------- install_binary_cp ---------- # systemd: nginx-lens + nginx-lens-worker; опционально infra в Docker. install_binary_cp() { fetch_artifact "nginx-lens_linux_${ARCH}.tar.gz" "nginx-lens" fetch_artifact "nginx-lens-worker_linux_${ARCH}.tar.gz" "nginx-lens-worker" if [ ! -f /etc/nginx-lens/api.env ]; then jwt="$(rand_hex 32)" boot="nlx_$(rand_hex 12)" host_ip="$(hostname -I 2>/dev/null | awk '{print $1}')" [ -n "$host_ip" ] || host_ip="127.0.0.1" cat >/etc/nginx-lens/api.env </etc/systemd/system/nginx-lens.service </etc/systemd/system/nginx-lens-worker.service </dev/null 2>&1; then useradd --system --home /var/lib/nginx-lens --shell /usr/sbin/nologin nginxlens 2>/dev/null || true fi chown -R nginxlens:nginxlens /var/lib/nginx-lens 2>/dev/null || true start_infra_compose systemctl daemon-reload systemctl enable nginx-lens nginx-lens-worker systemctl restart nginx-lens nginx-lens-worker echo "Control plane (binary) installed (${VERSION}, ${ARCH})" echo " binary: ${PREFIX}/nginx-lens" echo " worker: ${PREFIX}/nginx-lens-worker" echo " service: systemctl status nginx-lens nginx-lens-worker" echo " env: /etc/nginx-lens/api.env" echo " UI: binary path does not include UI — use --compose for full stack:" echo " curl -fsSL ${BASE_URL} | sh -s -- --compose" echo " API: see NL_PUBLIC_URL in /etc/nginx-lens/api.env" } while [ $# -gt 0 ]; do case "$1" in --agent) MODE="agent"; shift ;; --control-plane|--cp) MODE="control-plane"; shift ;; --compose) INSTALL_KIND="compose"; shift ;; --binary) INSTALL_KIND="binary"; shift ;; --api-url) API_URL="$2"; shift 2 ;; --version) VERSION="$2"; shift 2 ;; --prefix) PREFIX="$2"; shift 2 ;; --compose-dir) COMPOSE_DIR="$2"; shift 2 ;; -h|--help) sed -n '2,20p' "$0" exit 0 ;; *) echo "unknown arg: $1" >&2 exit 1 ;; esac done require_root ARCH="$(detect_arch)" TMP="$(mktemp -d)" trap 'rm -rf "$TMP"' EXIT install -d -m 755 "$PREFIX" /etc/nginx-lens /var/lib/nginx-lens if [ "$MODE" = "control-plane" ]; then kind="$INSTALL_KIND" if [ "$kind" = "auto" ]; then if have_compose; then kind="compose" else kind="binary" if have_docker && ! have_compose; then echo "warn: docker found but Compose v2 plugin missing — installing binaries only." >&2 echo " Install compose plugin, then: curl -fsSL ${BASE_URL} | sh -s -- --compose" >&2 fi fi fi if [ "$kind" = "compose" ]; then install_compose_stack else install_binary_cp fi elif [ "$MODE" = "agent" ]; then if [ -z "$API_URL" ]; then echo "usage: install.sh --agent --api-url URL" >&2 exit 1 fi fetch_artifact "nginx-lens-agent_linux_${ARCH}.tar.gz" "nginx-lens-agent" cat >/etc/nginx-lens/agent.env </etc/systemd/system/nginx-lens-agent.service <&2 exit 1 fi