a1a17e81a1
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.
369 lines
14 KiB
Bash
Executable File
369 lines
14 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
|
|
######################################################################################
|
|
#### install-webtop.sh — Deploy LinuxServer.io Webtop behind nginx reverse proxy ####
|
|
#### Installs Docker, runs Webtop container with persistent config, configures ####
|
|
#### nginx with basic auth and WebSocket proxy for KasmVNC. ####
|
|
#### Requires: Ubuntu 22.04+, root access ####
|
|
#### ####
|
|
#### Author: Phil Connor ####
|
|
#### Contact: contact@mylinux.work ####
|
|
#### License: MIT ####
|
|
#### Version 1.00 ####
|
|
#### ####
|
|
#### Usage: ####
|
|
#### sudo ./install-webtop.sh --domain webtop.example.com ####
|
|
#### sudo ./install-webtop.sh --domain webtop.example.com --no-ssl ####
|
|
#### sudo ./install-webtop.sh --domain webtop.example.com --allow-ip 1.2.3.4 ####
|
|
#### ####
|
|
#### See --help for all options. ####
|
|
######################################################################################
|
|
|
|
set -euo pipefail
|
|
|
|
# ── Defaults ──────────────────────────────────────────────────────────
|
|
DOMAIN=""
|
|
DESKTOP="fedora-mate"
|
|
WEBTOP_IMAGE=""
|
|
CONTAINER_NAME="webtop"
|
|
CONFIG_DIR="/opt/webtop"
|
|
INTERNAL_PORT="3000"
|
|
TIMEZONE="${TZ:-America/Chicago}"
|
|
SHM_SIZE="1gb"
|
|
ALLOW_IP=""
|
|
NO_SSL=false
|
|
AUTH_USER="admin"
|
|
AUTH_PASS=""
|
|
SKIP_DOCKER=false
|
|
|
|
# ── Colors ────────────────────────────────────────────────────────────
|
|
if [[ -t 1 ]]; then
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[0;33m'
|
|
RED='\033[0;31m'
|
|
BLUE='\033[0;34m'
|
|
BOLD='\033[1m'
|
|
RESET='\033[0m'
|
|
else
|
|
GREEN="" YELLOW="" RED="" BLUE="" BOLD="" RESET=""
|
|
fi
|
|
|
|
log() { echo -e "${GREEN}[OK]${RESET} $*"; }
|
|
warn() { echo -e "${YELLOW}[WARN]${RESET} $*"; }
|
|
err() { echo -e "${RED}[ERROR]${RESET} $*" >&2; }
|
|
step() { echo -e "\n${BOLD}── $* ──${RESET}"; }
|
|
|
|
# ── Help ──────────────────────────────────────────────────────────────
|
|
show_help() {
|
|
cat <<'EOF'
|
|
Usage: install-webtop.sh [OPTIONS]
|
|
|
|
Deploy LinuxServer.io Webtop (Fedora MATE) behind nginx reverse proxy
|
|
with basic auth, WebSocket support, and optional Let's Encrypt SSL.
|
|
|
|
Required:
|
|
--domain DOMAIN Domain name for nginx vhost and SSL cert
|
|
|
|
Options:
|
|
--desktop DESKTOP Desktop environment (default: fedora-mate)
|
|
Options: fedora-mate, fedora-xfce, fedora-kde,
|
|
ubuntu-mate, ubuntu-xfce, ubuntu-kde,
|
|
alpine-xfce, alpine-kde
|
|
--image IMAGE Override with a custom image tag
|
|
--name NAME Container name (default: webtop)
|
|
--config-dir PATH Persistent config directory (default: /opt/webtop)
|
|
--port PORT Internal KasmVNC port (default: 3000)
|
|
--tz TIMEZONE Timezone (default: America/Chicago)
|
|
--shm-size SIZE Shared memory size (default: 1gb)
|
|
--allow-ip IP Restrict access to this IP in UFW (optional)
|
|
--auth-user USER Basic auth username (default: admin)
|
|
--auth-pass PASS Basic auth password (prompted if not set)
|
|
--no-ssl Skip Let's Encrypt — use HTTP only
|
|
--skip-docker Skip Docker installation (already installed)
|
|
-h, --help Show this help
|
|
|
|
Examples:
|
|
sudo ./install-webtop.sh --domain webtop.example.com
|
|
sudo ./install-webtop.sh --domain webtop.example.com --allow-ip 203.0.113.50
|
|
sudo ./install-webtop.sh --domain webtop.example.com --desktop ubuntu-xfce
|
|
sudo ./install-webtop.sh --domain webtop.example.com --no-ssl --auth-pass s3cret
|
|
EOF
|
|
exit 0
|
|
}
|
|
|
|
# ── Parse Arguments ───────────────────────────────────────────────────
|
|
parse_args() {
|
|
while [[ $# -gt 0 ]]; do
|
|
case "$1" in
|
|
--domain) DOMAIN="$2"; shift 2 ;;
|
|
--desktop) DESKTOP="$2"; shift 2 ;;
|
|
--image) WEBTOP_IMAGE="$2"; shift 2 ;;
|
|
--name) CONTAINER_NAME="$2"; shift 2 ;;
|
|
--config-dir) CONFIG_DIR="$2"; shift 2 ;;
|
|
--port) INTERNAL_PORT="$2"; shift 2 ;;
|
|
--tz) TIMEZONE="$2"; shift 2 ;;
|
|
--shm-size) SHM_SIZE="$2"; shift 2 ;;
|
|
--allow-ip) ALLOW_IP="$2"; shift 2 ;;
|
|
--auth-user) AUTH_USER="$2"; shift 2 ;;
|
|
--auth-pass) AUTH_PASS="$2"; shift 2 ;;
|
|
--no-ssl) NO_SSL=true; shift ;;
|
|
--skip-docker) SKIP_DOCKER=true; shift ;;
|
|
-h|--help) show_help ;;
|
|
*) err "Unknown option: $1"; echo "Run with --help for usage."; exit 1 ;;
|
|
esac
|
|
done
|
|
|
|
if [[ -z "$DOMAIN" ]]; then
|
|
err "--domain is required"
|
|
exit 1
|
|
fi
|
|
|
|
# resolve image from desktop choice (--image overrides)
|
|
if [[ -z "$WEBTOP_IMAGE" ]]; then
|
|
local valid_desktops="fedora-mate fedora-xfce fedora-kde ubuntu-mate ubuntu-xfce ubuntu-kde alpine-xfce alpine-kde"
|
|
if ! echo "$valid_desktops" | grep -qw "$DESKTOP"; then
|
|
err "Unknown desktop: ${DESKTOP}"
|
|
echo "Valid options: ${valid_desktops}"
|
|
exit 1
|
|
fi
|
|
WEBTOP_IMAGE="lscr.io/linuxserver/webtop:${DESKTOP}"
|
|
fi
|
|
}
|
|
|
|
# ── Root Check ────────────────────────────────────────────────────────
|
|
check_root() {
|
|
if [[ $EUID -ne 0 ]]; then
|
|
err "This script must be run as root."
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
# ── Install Docker ────────────────────────────────────────────────────
|
|
install_docker() {
|
|
step "Docker"
|
|
|
|
if [[ "$SKIP_DOCKER" == "true" ]]; then
|
|
log "Skipping Docker install (--skip-docker)"
|
|
return
|
|
fi
|
|
|
|
if command -v docker &>/dev/null; then
|
|
log "Docker already installed: $(docker --version)"
|
|
return
|
|
fi
|
|
|
|
curl -fsSL https://get.docker.com | sh
|
|
systemctl enable --now docker
|
|
log "Docker installed: $(docker --version)"
|
|
}
|
|
|
|
# ── Create Config Directory ───────────────────────────────────────────
|
|
setup_config_dir() {
|
|
step "Config directory"
|
|
mkdir -p "${CONFIG_DIR}"
|
|
log "Persistent config: ${CONFIG_DIR}"
|
|
}
|
|
|
|
# ── Run Webtop Container ─────────────────────────────────────────────
|
|
run_webtop() {
|
|
step "Webtop container"
|
|
|
|
if docker ps -a --format '{{.Names}}' | grep -q "^${CONTAINER_NAME}$"; then
|
|
warn "Container '${CONTAINER_NAME}' already exists — removing"
|
|
docker rm -f "${CONTAINER_NAME}" >/dev/null 2>&1
|
|
fi
|
|
|
|
docker run -d \
|
|
--name "${CONTAINER_NAME}" \
|
|
--restart unless-stopped \
|
|
-p "127.0.0.1:${INTERNAL_PORT}:3000" \
|
|
-e PUID=1000 \
|
|
-e PGID=1000 \
|
|
-e "TZ=${TIMEZONE}" \
|
|
-v "${CONFIG_DIR}:/config" \
|
|
--shm-size="${SHM_SIZE}" \
|
|
"${WEBTOP_IMAGE}"
|
|
|
|
log "Container '${CONTAINER_NAME}' running (${WEBTOP_IMAGE})"
|
|
log "Bound to 127.0.0.1:${INTERNAL_PORT} (nginx will proxy)"
|
|
}
|
|
|
|
# ── Install nginx ─────────────────────────────────────────────────────
|
|
install_nginx() {
|
|
step "nginx"
|
|
|
|
if ! command -v nginx &>/dev/null; then
|
|
apt-get update -qq
|
|
apt-get install -y -qq nginx apache2-utils >/dev/null
|
|
log "nginx installed"
|
|
else
|
|
log "nginx already installed"
|
|
if ! command -v htpasswd &>/dev/null; then
|
|
apt-get install -y -qq apache2-utils >/dev/null
|
|
fi
|
|
fi
|
|
}
|
|
|
|
# ── Basic Auth ────────────────────────────────────────────────────────
|
|
setup_auth() {
|
|
step "Basic auth"
|
|
|
|
if [[ -z "$AUTH_PASS" ]]; then
|
|
AUTH_PASS=$(openssl rand -base64 16)
|
|
fi
|
|
|
|
htpasswd -bc /etc/nginx/.htpasswd_webtop "${AUTH_USER}" "${AUTH_PASS}" 2>/dev/null
|
|
chown root:www-data /etc/nginx/.htpasswd_webtop
|
|
chmod 640 /etc/nginx/.htpasswd_webtop
|
|
|
|
# save credentials to a root-only file
|
|
local creds_file="${CONFIG_DIR}/.credentials"
|
|
echo "username=${AUTH_USER}" > "${creds_file}"
|
|
echo "password=${AUTH_PASS}" >> "${creds_file}"
|
|
chmod 600 "${creds_file}"
|
|
|
|
log "Basic auth configured (user: ${AUTH_USER})"
|
|
log "Credentials saved to ${creds_file} (root-only)"
|
|
}
|
|
|
|
# ── nginx Vhost ───────────────────────────────────────────────────────
|
|
configure_nginx() {
|
|
step "nginx vhost"
|
|
|
|
cat > "/etc/nginx/sites-available/${DOMAIN}" <<NGINX
|
|
server {
|
|
listen 80;
|
|
listen [::]:80;
|
|
server_name ${DOMAIN};
|
|
|
|
auth_basic "Webtop";
|
|
auth_basic_user_file /etc/nginx/.htpasswd_webtop;
|
|
|
|
location / {
|
|
proxy_pass http://127.0.0.1:${INTERNAL_PORT};
|
|
proxy_http_version 1.1;
|
|
proxy_set_header Upgrade \$http_upgrade;
|
|
proxy_set_header Connection "upgrade";
|
|
proxy_set_header Host \$host;
|
|
proxy_set_header X-Real-IP \$remote_addr;
|
|
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
|
proxy_set_header X-Forwarded-Proto \$scheme;
|
|
|
|
proxy_buffering off;
|
|
proxy_read_timeout 86400s;
|
|
proxy_send_timeout 86400s;
|
|
}
|
|
}
|
|
NGINX
|
|
|
|
ln -sf "/etc/nginx/sites-available/${DOMAIN}" /etc/nginx/sites-enabled/
|
|
|
|
nginx -t 2>&1
|
|
systemctl reload nginx
|
|
log "nginx vhost configured: ${DOMAIN}"
|
|
}
|
|
|
|
# ── Let's Encrypt SSL ────────────────────────────────────────────────
|
|
setup_ssl() {
|
|
step "SSL (Let's Encrypt)"
|
|
|
|
if [[ "$NO_SSL" == "true" ]]; then
|
|
warn "Skipping SSL (--no-ssl) — access via http://${DOMAIN}"
|
|
return
|
|
fi
|
|
|
|
if ! command -v certbot &>/dev/null; then
|
|
apt-get install -y -qq certbot python3-certbot-nginx >/dev/null
|
|
log "certbot installed"
|
|
fi
|
|
|
|
certbot --nginx -d "${DOMAIN}" --non-interactive --agree-tos \
|
|
--register-unsafely-without-email --redirect
|
|
|
|
log "SSL configured: https://${DOMAIN}"
|
|
}
|
|
|
|
# ── UFW Rules ─────────────────────────────────────────────────────────
|
|
setup_ufw() {
|
|
step "Firewall"
|
|
|
|
if ! command -v ufw &>/dev/null; then
|
|
warn "UFW not found — skipping firewall config"
|
|
return
|
|
fi
|
|
|
|
ufw allow OpenSSH >/dev/null 2>&1 || true
|
|
ufw allow 'Nginx Full' >/dev/null 2>&1 || true
|
|
|
|
if [[ -n "$ALLOW_IP" ]]; then
|
|
ufw deny from any to any port 80 >/dev/null 2>&1 || true
|
|
ufw deny from any to any port 443 >/dev/null 2>&1 || true
|
|
ufw allow from "${ALLOW_IP}" to any port 80 >/dev/null 2>&1 || true
|
|
ufw allow from "${ALLOW_IP}" to any port 443 >/dev/null 2>&1 || true
|
|
log "Access restricted to ${ALLOW_IP}"
|
|
else
|
|
log "nginx ports open (no IP restriction)"
|
|
warn "Consider using --allow-ip to restrict access"
|
|
fi
|
|
|
|
if ! ufw status | grep -q "^Status: active"; then
|
|
ufw --force enable >/dev/null 2>&1
|
|
log "UFW enabled"
|
|
fi
|
|
}
|
|
|
|
# ── Summary ───────────────────────────────────────────────────────────
|
|
print_summary() {
|
|
local proto="http"
|
|
if [[ "$NO_SSL" == "false" ]]; then
|
|
proto="https"
|
|
fi
|
|
|
|
echo ""
|
|
echo "────────────────────────────────────────"
|
|
echo -e "${BOLD}Webtop Deployment Complete${RESET}"
|
|
echo "────────────────────────────────────────"
|
|
echo " URL: ${proto}://${DOMAIN}"
|
|
echo " Username: ${AUTH_USER}"
|
|
echo " Password: ${AUTH_PASS}"
|
|
echo " Creds file: ${CONFIG_DIR}/.credentials"
|
|
echo " Image: ${WEBTOP_IMAGE}"
|
|
echo " Container: ${CONTAINER_NAME}"
|
|
echo " Config: ${CONFIG_DIR}"
|
|
echo " Timezone: ${TIMEZONE}"
|
|
if [[ -n "$ALLOW_IP" ]]; then
|
|
echo " Allowed IP: ${ALLOW_IP}"
|
|
fi
|
|
echo "────────────────────────────────────────"
|
|
echo ""
|
|
echo "Manage:"
|
|
echo " docker logs ${CONTAINER_NAME} # view logs"
|
|
echo " docker restart ${CONTAINER_NAME} # restart"
|
|
echo " docker stop ${CONTAINER_NAME} # stop"
|
|
echo " docker start ${CONTAINER_NAME} # start"
|
|
echo " ls ${CONFIG_DIR}/ # persistent data"
|
|
}
|
|
|
|
# ── Main ──────────────────────────────────────────────────────────────
|
|
main() {
|
|
parse_args "$@"
|
|
check_root
|
|
|
|
echo -e "${BOLD}Webtop Installer${RESET}"
|
|
echo "Domain: ${DOMAIN}"
|
|
echo "Image: ${WEBTOP_IMAGE}"
|
|
echo "Config: ${CONFIG_DIR}"
|
|
|
|
install_docker
|
|
setup_config_dir
|
|
run_webtop
|
|
install_nginx
|
|
setup_auth
|
|
configure_nginx
|
|
setup_ssl
|
|
setup_ufw
|
|
print_summary
|
|
}
|
|
|
|
main "$@"
|