install.sh
Rama main · réplica de lectura del repositorio original.
#!/usr/bin/env bash
#
# nginx-webui — interactive installer.
#
# Offers two deployment modes:
# 1. Docker + host control agent : web-UI container managing nginx installed
# on this machine (status/check/reload/restart via the host agent)
# 2. Manual (all on host) : nginx and the web UI installed directly
# on this machine (systemd + venv)
#
# Usage: sudo ./install.sh (or: ./install.sh --check | --help)
# One-liner: curl -fsSL https://raw.githubusercontent.com/himalsimkhada/nginx-webui/main/install.sh | bash
# Headless: NGINX_MODE=1 WEBUI_PASSWORD=x TARGET_DIR=~/nginx-webui curl -fsSL ... | bash
set -euo pipefail
REPO_URL="https://github.com/himalsimkhada/nginx-webui.git"
WEBUI_REPO_URL="https://github.com/himalsimkhada/webui.git"
WEBUI_PORT="${WEBUI_PORT:-8080}"
DIR="$(cd "$(dirname "$0")" && pwd)"
cd "$DIR"
# ── Output helpers ───────────────────────────────────────────────────────
if [ -t 1 ]; then
C_RESET=$'\e[0m'; C_GREEN=$'\e[32m'; C_YELLOW=$'\e[33m'; C_RED=$'\e[31m'; C_BOLD=$'\e[1m'
else
C_RESET=""; C_GREEN=""; C_YELLOW=""; C_RED=""; C_BOLD=""
fi
info() { printf '%s\n' "${C_BOLD}==>${C_RESET} $*"; }
ok() { printf '%s%s%s\n' "${C_GREEN} $*${C_RESET}"; }
warn() { printf '%s%s%s\n' "${C_YELLOW}!! $*${C_RESET}"; }
die() { printf '%s%s%s\n' "${C_RED}FATAL:$*${C_RESET}" >&2; exit 1; }
has_cmd() { command -v "$1" >/dev/null 2>&1; }
# ── Input helpers ────────────────────────────────────────────────────────
# Under `curl ... | bash` stdin is the script stream, and a nested run
# inherits that consumed stream, so bare `read` gets EOF instantly. Read from
# the controlling terminal when one exists; headless runs use env vars.
have_tty() { ( exec </dev/tty ) 2>/dev/null; }
read_input() {
local var="$1" prompt="${2:-}"
local envval=""
case "$var" in
target) envval="${TARGET_DIR:-}";;
choice) envval="${NGINX_MODE:-}";;
ans) envval="${NGINX_YES:-}";;
esac
if [ -n "$envval" ]; then
printf -v "$var" '%s' "$envval"
return 0
fi
if have_tty; then
read -r -p "$prompt" "$var" < /dev/tty
else
die "No terminal available and \$$var was not set (${prompt%:}). Re-run from a terminal or set the env var."
fi
}
read_input_silent() {
local var="$1" prompt="${2:-}"
if have_tty; then
read -r -s -p "$prompt" "$var" < /dev/tty
else
die "No terminal available for password input. Set WEBUI_PASSWORD=... and re-run."
fi
echo ""
}
# ── Self-bootstrap ────────────────────────────────────────────────────────
# Support one-liner installs (curl ... | bash): when the script is streamed
# there is no repo checkout in $DIR, so fetch the repository first and then
# re-run this installer from inside it (finishes with the user's chosen mode).
if [ ! -f "$DIR/app.py" ] || [ ! -f "$DIR/docker-compose.yml" ]; then
echo "==> One-liner install: no repo checkout in \"$DIR\"."
has_cmd git || die "git is required for the one-liner install (curl | bash)."
has_cmd curl || has_cmd wget || warn "Neither curl nor wget found; check the URL you piped."
default_target="$HOME/nginx-webui"
target=""
read_input target "Install the project into [$default_target]: " || true
target="${target:-$default_target}"
mkdir -p "$(dirname "$target")"
if [ -d "$target" ] && [ -f "$target/app.py" ]; then
info "Updating existing checkout at $target"
(cd "$target" && git pull --ff-only) >/dev/null 2>&1 || true
else
info "Cloning $REPO_URL into $target"
git clone --quiet --depth 1 "$REPO_URL" "$target"
fi
cd "$target"
exec bash "$target/install.sh" "$@"
fi
# ── System detection ─────────────────────────────────────────────────────
OS_ID="unknown"
OS_NAME="unknown"
if [ -r /etc/os-release ]; then
# shellcheck disable=SC1091
. /etc/os-release
OS_ID="${ID:-unknown}"
OS_NAME="${NAME:-$OS_ID}"
fi
is_deb() { case "$OS_ID" in debian|ubuntu|linuxmint|pop|elementary|kali|raspbian) return 0;; *) return 1;; esac; }
is_rpm() { case "$OS_ID" in rhel|fedora|centos|almalinux|rocky|ol|amazon) return 0;; *) return 1;; esac; }
is_arch() { case "$OS_ID" in arch|manjaro|endeavouros) return 0;; *) return 1;; esac; }
pkg_install() {
if is_deb; then
sudo apt-get update -qq
sudo apt-get install -y -qq "$@"
elif is_rpm; then
if command -v dnf >/dev/null 2>&1; then sudo dnf install -y "$@"
else sudo yum install -y "$@"; fi
elif is_arch; then
sudo pacman -S --noconfirm --needed "$@"
else
die "Unsupported distro ($OS_NAME). Please install dependencies manually."
fi
}
# ── Generic helpers ──────────────────────────────────────────────────────
random_secret() {
if has_cmd openssl; then
openssl rand -hex 32
else
od -An -N32 -tx1 /dev/urandom | tr -d ' \n'
fi
}
ask_password() {
# Accepts WEBUI_PASSWORD from the environment (headless runs) or prompts.
if [ -z "${WEBUI_PASSWORD:-}" ]; then
WEBUI_PASSWORD=""
while [ -z "$WEBUI_PASSWORD" ]; do
read_input_silent WEBUI_PASSWORD " Web UI password (used to log in): "
if [ -z "$WEBUI_PASSWORD" ]; then
warn "Password cannot be empty. Leaving it blank is not supported."
else
read_input_silent WEBUI_PASSWORD_CONFIRM " Confirm password: "
if [ "$WEBUI_PASSWORD" != "$WEBUI_PASSWORD_CONFIRM" ]; then
warn "Passwords do not match. Try again."
WEBUI_PASSWORD=""
fi
fi
done
else
ok "Using WEBUI_PASSWORD from the environment"
fi
SECRET_KEY="$(random_secret)"
}
write_env_file() {
# Writes .env from an array of KEY=VALUE lines passed on stdin.
local envpath="$DIR/.env"
info "Writing $envpath"
cat > "$envpath"
chmod 600 "$envpath" 2>/dev/null || true
}
# ── webui admin facade (companion dashboard) ────────────────────────────
webui_detected() {
# True when the webui admin facade is already answering on the expected port.
if has_cmd curl && curl -fsS --max-time 3 "http://127.0.0.1:${WEBUI_PORT}/healthz" >/dev/null 2>&1; then
return 0
fi
if has_cmd docker && docker ps --format '{{.Names}}' 2>/dev/null | grep -q '^webui-admin$'; then
return 0
fi
return 1
}
offer_webui_portal() {
# The dashboard UI lives in the companion webui facade. If it is not running,
# offer to install it now.
local tgt="$HOME/webui"
if webui_detected; then
ok "webui admin dashboard already running on http://127.0.0.1:${WEBUI_PORT}"
return
fi
if [ -d "$tgt" ] && [ -f "$tgt/install.sh" ]; then
warn "The webui admin dashboard is NOT running but a checkout exists at $tgt (port $WEBUI_PORT)."
else
warn "The webui admin dashboard (himalsimkhada/webui) was not detected on http://127.0.0.1:${WEBUI_PORT}."
fi
info "The nginx dashboard is hosted by the webui facade; without it you only get the API."
read_input ans " Install the webui admin dashboard now? [Y/n] "
if [ "${ans:-y}" = "n" ] || [ "${ans:-y}" = "N" ]; then
warn "Install it later with: curl -fsSL https://raw.githubusercontent.com/himalsimkhada/webui/main/install.sh | bash"
return
fi
has_cmd git || die "git is required to clone the webui dashboard."
mkdir -p "$HOME"
if [ ! -f "$tgt/install.sh" ]; then
info "Cloning $WEBUI_REPO_URL into $tgt"
git clone --quiet --depth 1 "$WEBUI_REPO_URL" "$tgt"
fi
info "Running the webui dashboard installer (choose Docker or Manual mode)"
bash "$tgt/install.sh"
ok "webui admin dashboard installed. Open http://localhost:${WEBUI_PORT}"
}
# ── nginx + host control agent ───────────────────────────────────────────
nginx_packages() {
if is_deb; then echo "nginx"
elif is_rpm; then echo "nginx"
elif is_arch; then echo "nginx"
fi
}
ensure_host_nginx() {
# The managed nginx must live on this machine (the docker container edits
# the mounted /etc/nginx, and the agent signals its master).
local ngconf="/etc/nginx/nginx.conf"
if ! has_cmd nginx; then
warn "nginx is not installed."
read_input ans " Install nginx now? [y/N] "
if [ "${ans:-n}" != "y" ] && [ "${ans:-n}" != "Y" ]; then
die "nginx is required. Re-run after installing nginx."
fi
# shellcheck disable=SC2046
pkg_install $(nginx_packages)
fi
has_cmd nginx || die "nginx is required"
if [ ! -f "$ngconf" ]; then
die "Expected nginx config at $ngconf but it is missing."
fi
ok "nginx present ($ngconf)"
}
ensure_host_agent() {
info "Installing the host control agent so the container can manage"
info "the HOST nginx (PID namespaces block direct signals; see README)."
sudo cp "$DIR/host_agent.py" /usr/local/bin/nginx-webui-host-agent.py
sudo chmod 755 /usr/local/bin/nginx-webui-host-agent.py
sudo cp "$DIR/nginx-webui-host-agent.service" /etc/systemd/system/
sudo systemctl daemon-reload
sudo systemctl enable --now nginx-webui-host-agent >/dev/null 2>&1
sleep 1
if curl -fsS http://127.0.0.1:9401/status >/dev/null 2>&1; then
ok "Host control agent active on :9401 (binds 0.0.0.0 for containers)"
else
warn "Agent installed but not answering yet. Check: sudo systemctl status nginx-webui-host-agent"
fi
}
ensure_docker() {
if ! has_cmd docker || ! docker compose version >/dev/null 2>&1; then
warn "Docker with the compose plugin is required but not installed."
read_input ans " Install Docker now? [y/N] " ans
if [ "${ans:-n}" != "y" ] && [ "${ans:-n}" != "Y" ]; then
die "Docker is required for this mode. Re-run after installing Docker."
fi
info "Installing Docker"
if is_deb; then
sudo apt-get update -qq
sudo apt-get install -y -qq docker.io docker-compose-v2
elif is_rpm; then
sudo dnf install -y moby-engine docker-compose-plugin 2>/dev/null \
|| sudo dnf install -y moby-engine || \
warn "Could not auto-install Docker. Install it manually and re-run."
elif is_arch; then
sudo pacman -S --noconfirm --needed docker docker-compose 2>/dev/null \
|| sudo pacman -S --noconfirm --needed docker
else
die "Unsupported distro for automatic Docker install. Install Docker manually."
fi
fi
if ! docker compose version >/dev/null 2>&1; then
die "Docker compose plugin is missing. Install docker-compose and re-run."
fi
if ! docker info >/dev/null 2>&1; then
warn "Docker daemon is not reachable. Starting the docker service..."
sudo systemctl enable --now docker >/dev/null 2>&1 || true
sleep 2
fi
docker info >/dev/null 2>&1 \
|| die "Docker daemon is not reachable. Start it (sudo systemctl start docker) and re-run."
ok "Docker + compose plugin available and the daemon is running"
}
ensure_python_tools() {
if is_deb; then
pkg_install python3 python3-venv
elif is_rpm; then
pkg_install python3 python3-pip
elif is_arch; then
pkg_install python python-virtualenv
fi
has_cmd python3 || die "python3 is required"
}
# ── Mode 1: Docker + host control agent ──────────────────────────────────
mode_docker() {
info "Mode 1: Docker + host control agent (web UI container, host nginx)"
ensure_docker
ensure_host_nginx
ensure_host_agent
ask_password
write_env_file <<EOF
WEBUI_PASSWORD=$WEBUI_PASSWORD
SECRET_KEY=$SECRET_KEY
NGINX_WEBUI_PORT=8400
NGINX_CONF_DIR=/etc/nginx
SERVICE_NAME=nginx-webui
LOG_FILE=
# Status/control are provided by the host control agent (installed above).
NGINX_STATUS_URL=
NGINX_CTL_URL=http://host.docker.internal:9401
EOF
info "Starting the web UI container (pulls the image on first run)"
docker compose up -d
ok "Deployed. Open http://localhost:8400"
ok "The dashboard manages the HOST nginx (/etc/nginx mounted, agent on :9401)."
offer_webui_portal
}
# ── Mode 2: Manual (all on host) ─────────────────────────────────────────
mode_manual() {
info "Mode 2: Manual install (nginx + web UI on this machine)"
ensure_host_nginx
ensure_python_tools
info "Setting up Python venv"
python3 -m venv "$DIR/venv"
"$DIR/venv/bin/pip" install -q -r "$DIR/requirements.txt"
ok "Dependencies installed"
ask_password
info "Writing credentials to /etc/nginx-webui.env"
local envout="/etc/nginx-webui.env"
sudo tee "$envout" >/dev/null <<EOF
WEBUI_PASSWORD=$WEBUI_PASSWORD
SECRET_KEY=$SECRET_KEY
EOF
sudo chmod 600 "$envout"
info "Installing systemd service"
{
echo "[Unit]"
echo "Description=nginx-webui backend (API-only)"
echo "After=network-online.target nginx.service"
echo "Wants=network-online.target"
echo "Wants=nginx.service"
echo ""
echo "[Service]"
echo "Type=simple"
echo "User=root"
echo "WorkingDirectory=$DIR"
echo "ExecStart=$DIR/venv/bin/python app.py"
echo "EnvironmentFile=-/etc/nginx-webui.env"
echo "Restart=on-failure"
echo "RestartSec=3"
echo ""
echo "[Install]"
echo "WantedBy=multi-user.target"
} | sudo tee /etc/systemd/system/nginx-webui.service >/dev/null
sudo systemctl daemon-reload
sudo systemctl enable nginx-webui
sudo systemctl restart nginx-webui
ok "Deployed. Open http://localhost:8400"
ok "Manage with: sudo systemctl status nginx-webui"
offer_webui_portal
}
# ── Main menu / flags ────────────────────────────────────────────────────
show_menu() {
echo ""
echo "Select how you want to run the nginx Web UI:"
echo ""
echo " 1) Docker + host agent - web UI container managing nginx on THIS machine (recommended)"
echo " 2) Manual - nginx and the web UI both installed directly on THIS machine"
echo ""
while :; do
read_input choice "Enter your choice [1-2]: "
case "$choice" in
1) mode_docker; return;;
2) mode_manual; return;;
*) warn "Please choose 1 or 2.";;
esac
done
}
do_check() {
echo "── System check ─────────────────────────────"
echo "Distro : $OS_NAME ($OS_ID)"
echo "Package tool : $(is_deb && echo 'apt' || (is_rpm && echo 'rpm/dnf' || (is_arch && echo 'pacman' || echo 'unknown')))"
echo "nginx conf : /etc/nginx/nginx.conf"
echo ""
if has_cmd nginx; then
echo "nginx : installed ($(nginx -v 2>&1 | sed 's/^nginx version: //'))"
else
echo "nginx : NOT installed"
fi
if curl -fsS http://127.0.0.1:9401/status >/dev/null 2>&1; then
echo "host agent : active on 127.0.0.1:9401"
else
echo "host agent : not running"
fi
if has_cmd docker && docker compose version >/dev/null 2>&1; then
echo "Docker+compose: available"
else
echo "Docker+compose: missing"
fi
if has_cmd python3; then
echo "python3 : $(command -v python3)"
else
echo "python3 : missing"
fi
echo ""
}
case "${1:-}" in
--check|-c) do_check; exit 0;;
--help|-h)
sed -n '1,12p' "$0" | sed 's/^# \{0,1\}//'; exit 0;;
"")
if [ "$(id -u)" -eq 0 ]; then
warn "Running as root. Prefer running as a normal sudo user on some distros."
fi
show_menu
echo ""
echo "Done!"
;;
*) echo "Unknown option: $1 (use --help)"; exit 1;;
esac