8590eda2c2
- New modes: lan-http, lan-tls (self-signed), public, full - Add flags: --no-sni443, --enable-web-https (lan-tls), --test-renewal - generate-config: lan-tls HTTPS on 8443 only when explicitly enabled; HSTS only in full; SNI 443 default in full - detect-environment: remove interactive prompt; adjust public description to 'HTTP + TURN' - deploy.sh: pass new flags, add certbot scheduler (systemd timer or cron fallback), add dry-run renewal test - Docs (EN/zh-CN): update quick start, modes overview, LAN TLS guidance, LE auto-issue/renew section
488 lines
12 KiB
Bash
Executable File
488 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
||
|
||
# Color definitions
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# Global variables
|
||
NETWORK_MODE=""
|
||
LOCAL_IP=""
|
||
PUBLIC_IP=""
|
||
DEPLOYMENT_MODE="basic"
|
||
FORCED_MODE=""
|
||
LOCAL_IP_OVERRIDE=""
|
||
|
||
declare -a IP_CANDIDATES=()
|
||
declare -A __SEEN_IPS=()
|
||
|
||
add_ip_candidate() {
|
||
local ip="$1"
|
||
[[ -z "$ip" ]] && return
|
||
[[ "$ip" == "127."* ]] && return
|
||
[[ "$ip" == "0.0.0.0" ]] && return
|
||
if [[ -z "${__SEEN_IPS[$ip]}" ]]; then
|
||
IP_CANDIDATES+=("$ip")
|
||
__SEEN_IPS[$ip]=1
|
||
fi
|
||
}
|
||
|
||
is_rfc1918_ip() {
|
||
local ip="$1"
|
||
case "$ip" in
|
||
10.*|192.168.*|172.1[6-9].*|172.2[0-9].*|172.3[0-1].*)
|
||
return 0
|
||
;;
|
||
*)
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
is_cgnat_ip() {
|
||
local ip="$1"
|
||
case "$ip" in
|
||
100.*)
|
||
return 0
|
||
;;
|
||
*)
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
is_reserved_benchmark_ip() {
|
||
local ip="$1"
|
||
case "$ip" in
|
||
198.18.*|198.19.*)
|
||
return 0
|
||
;;
|
||
*)
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
is_link_local_ip() {
|
||
local ip="$1"
|
||
case "$ip" in
|
||
169.254.*)
|
||
return 0
|
||
;;
|
||
*)
|
||
return 1
|
||
;;
|
||
esac
|
||
}
|
||
|
||
is_routable_public_ip() {
|
||
local ip="$1"
|
||
if [[ -z "$ip" ]]; then
|
||
return 1
|
||
fi
|
||
if is_rfc1918_ip "$ip"; then
|
||
return 1
|
||
fi
|
||
if is_cgnat_ip "$ip"; then
|
||
return 1
|
||
fi
|
||
if is_reserved_benchmark_ip "$ip"; then
|
||
return 1
|
||
fi
|
||
case "$ip" in
|
||
127.*|169.254.*)
|
||
return 1
|
||
;;
|
||
*)
|
||
return 0
|
||
;;
|
||
esac
|
||
}
|
||
|
||
collect_ip_candidates() {
|
||
IP_CANDIDATES=()
|
||
unset __SEEN_IPS
|
||
declare -A __SEEN_IPS=()
|
||
|
||
if command -v hostname >/dev/null 2>&1; then
|
||
local host_ips
|
||
host_ips=$(hostname -I 2>/dev/null || true)
|
||
for ip in $host_ips; do
|
||
add_ip_candidate "$ip"
|
||
done
|
||
fi
|
||
|
||
if command -v ip >/dev/null 2>&1; then
|
||
while IFS= read -r ip; do
|
||
add_ip_candidate "$ip"
|
||
done < <(ip -o -4 addr show scope global 2>/dev/null | awk '{print $4}' | cut -d/ -f1)
|
||
fi
|
||
|
||
if command -v ifconfig >/dev/null 2>&1; then
|
||
while IFS= read -r ip; do
|
||
add_ip_candidate "$ip"
|
||
done < <(ifconfig 2>/dev/null | awk '/inet / {print $2}' | grep -E '^[0-9]+(\.[0-9]+){3}$')
|
||
fi
|
||
|
||
if command -v ip >/dev/null 2>&1; then
|
||
local route_ip
|
||
route_ip=$(ip route get 1.1.1.1 2>/dev/null | awk '{for (i=1;i<=NF;i++) if ($i=="src") {print $(i+1); exit}}')
|
||
add_ip_candidate "$route_ip"
|
||
fi
|
||
|
||
if [[ ${#IP_CANDIDATES[@]} -eq 0 ]]; then
|
||
local fallback
|
||
fallback=$(hostname -I 2>/dev/null | awk '{print $1}')
|
||
add_ip_candidate "$fallback"
|
||
fi
|
||
}
|
||
|
||
resolve_local_ip() {
|
||
if [[ -n "$LOCAL_IP_OVERRIDE" ]]; then
|
||
LOCAL_IP="$LOCAL_IP_OVERRIDE"
|
||
return
|
||
fi
|
||
|
||
collect_ip_candidates
|
||
|
||
if [[ ${#IP_CANDIDATES[@]} -eq 0 ]]; then
|
||
LOCAL_IP=""
|
||
return
|
||
fi
|
||
|
||
local ip
|
||
for ip in "${IP_CANDIDATES[@]}"; do
|
||
if is_rfc1918_ip "$ip"; then
|
||
LOCAL_IP="$ip"
|
||
return
|
||
fi
|
||
done
|
||
|
||
for ip in "${IP_CANDIDATES[@]}"; do
|
||
if is_cgnat_ip "$ip"; then
|
||
LOCAL_IP="$ip"
|
||
return
|
||
fi
|
||
done
|
||
|
||
for ip in "${IP_CANDIDATES[@]}"; do
|
||
if is_reserved_benchmark_ip "$ip"; then
|
||
continue
|
||
fi
|
||
if is_link_local_ip "$ip"; then
|
||
continue
|
||
fi
|
||
LOCAL_IP="$ip"
|
||
return
|
||
done
|
||
|
||
LOCAL_IP="${IP_CANDIDATES[0]}"
|
||
}
|
||
|
||
# Logging helpers
|
||
log_info() {
|
||
echo -e "${BLUE}ℹ️ $1${NC}"
|
||
}
|
||
|
||
log_success() {
|
||
echo -e "${GREEN}✅ $1${NC}"
|
||
}
|
||
|
||
log_warning() {
|
||
echo -e "${YELLOW}⚠️ $1${NC}"
|
||
}
|
||
|
||
log_error() {
|
||
echo -e "${RED}❌ $1${NC}"
|
||
}
|
||
|
||
# Detect network environment
|
||
detect_network_environment() {
|
||
log_info "Detecting network environment..."
|
||
|
||
resolve_local_ip
|
||
|
||
if [[ -z "$LOCAL_IP" ]]; then
|
||
LOCAL_IP="127.0.0.1"
|
||
log_warning "Unable to detect host IP; using default: $LOCAL_IP"
|
||
fi
|
||
|
||
if [[ "$FORCED_MODE" == "private" ]]; then
|
||
NETWORK_MODE="private"
|
||
PUBLIC_IP=""
|
||
log_info "Network mode set via parameters: $NETWORK_MODE"
|
||
echo " Local IP: $LOCAL_IP"
|
||
return 0
|
||
fi
|
||
|
||
local mode_guess="private"
|
||
local printed_prompt_info="false"
|
||
PUBLIC_IP=""
|
||
|
||
if curl -s --connect-timeout 5 --max-time 10 ifconfig.me > /dev/null 2>&1; then
|
||
PUBLIC_IP=$(curl -s --connect-timeout 5 --max-time 10 ifconfig.me 2>/dev/null || echo "")
|
||
if [[ -n "$PUBLIC_IP" ]]; then
|
||
if is_routable_public_ip "$PUBLIC_IP"; then
|
||
mode_guess="public"
|
||
else
|
||
log_warning "Public IP is test/reserved range; treating as private"
|
||
fi
|
||
else
|
||
log_warning "Public connectivity unstable; treating as private"
|
||
fi
|
||
fi
|
||
|
||
if [[ -z "$FORCED_MODE" ]]; then
|
||
if [[ "$mode_guess" == "public" ]]; then
|
||
NETWORK_MODE="public"
|
||
else
|
||
NETWORK_MODE="private"
|
||
fi
|
||
else
|
||
NETWORK_MODE="$FORCED_MODE"
|
||
if [[ "$FORCED_MODE" == "public" && -z "$PUBLIC_IP" ]]; then
|
||
log_warning "Could not detect public IP; continuing as public mode. Please verify network config"
|
||
fi
|
||
fi
|
||
|
||
if [[ "$NETWORK_MODE" != "public" ]]; then
|
||
PUBLIC_IP=""
|
||
fi
|
||
|
||
if [[ "$FORCED_MODE" == "public" ]]; then
|
||
log_info "Network mode set via parameters: $NETWORK_MODE"
|
||
elif [[ "$NETWORK_MODE" == "public" ]]; then
|
||
log_success "Public network detected"
|
||
else
|
||
log_success "Private network detected"
|
||
fi
|
||
|
||
if [[ "$printed_prompt_info" == "false" ]]; then
|
||
echo " Local IP: $LOCAL_IP"
|
||
if [[ "$NETWORK_MODE" == "public" && -n "$PUBLIC_IP" ]]; then
|
||
echo " Public IP: $PUBLIC_IP"
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Check system resources
|
||
check_system_resources() {
|
||
log_info "Checking system resources..."
|
||
|
||
local warnings=0
|
||
|
||
# Check memory
|
||
if command -v free >/dev/null 2>&1; then
|
||
TOTAL_MEM=$(free -m | awk 'NR==2{print $2}')
|
||
if [[ $TOTAL_MEM -lt 512 ]]; then
|
||
log_error "Insufficient memory: ${TOTAL_MEM}MB (512MB+ recommended)"
|
||
return 1
|
||
elif [[ $TOTAL_MEM -lt 1024 ]]; then
|
||
log_warning "Low memory: ${TOTAL_MEM}MB (1GB+ recommended)"
|
||
warnings=$((warnings + 1))
|
||
else
|
||
log_success "Memory OK: ${TOTAL_MEM}MB"
|
||
fi
|
||
else
|
||
log_warning "Unable to read memory usage"
|
||
warnings=$((warnings + 1))
|
||
fi
|
||
|
||
# Check disk usage
|
||
DISK_USAGE=$(df -h / | awk 'NR==2{print $5}' | sed 's/%//')
|
||
if [[ $DISK_USAGE -gt 95 ]]; then
|
||
log_error "Insufficient disk space: ${DISK_USAGE}% used"
|
||
return 1
|
||
elif [[ $DISK_USAGE -gt 80 ]]; then
|
||
log_warning "Disk space tight: ${DISK_USAGE}% used"
|
||
warnings=$((warnings + 1))
|
||
else
|
||
log_success "Disk space OK: ${DISK_USAGE}% used"
|
||
fi
|
||
|
||
# Check available disk space
|
||
AVAILABLE_SPACE=$(df -BG / | awk 'NR==2{print $4}' | sed 's/G//')
|
||
if [[ $AVAILABLE_SPACE -lt 2 ]]; then
|
||
log_error "Not enough free disk space: ${AVAILABLE_SPACE}GB (2GB+ recommended)"
|
||
return 1
|
||
fi
|
||
|
||
if [[ $warnings -gt 0 ]]; then
|
||
log_warning "System resource check passed with $warnings warning(s)"
|
||
else
|
||
log_success "System resource check passed"
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
# Validate Docker environment
|
||
verify_docker_installation() {
|
||
log_info "Checking Docker environment..."
|
||
|
||
if ! command -v docker &> /dev/null; then
|
||
log_error "Docker is not installed"
|
||
echo "Please install Docker: https://docs.docker.com/get-docker/"
|
||
return 1
|
||
fi
|
||
|
||
if ! command -v docker-compose &> /dev/null && ! docker compose version &> /dev/null; then
|
||
log_error "Docker Compose is not installed"
|
||
echo "Please install Docker Compose: https://docs.docker.com/compose/install/"
|
||
return 1
|
||
fi
|
||
|
||
# Check Docker service status
|
||
if ! docker info &> /dev/null; then
|
||
log_error "Docker service is not running"
|
||
echo "Please start the Docker service"
|
||
return 1
|
||
fi
|
||
|
||
# Check Docker version
|
||
DOCKER_VERSION=$(docker --version | grep -oE '[0-9]+\.[0-9]+' | head -1)
|
||
log_success "Docker version: $DOCKER_VERSION"
|
||
|
||
# Check Docker Compose version
|
||
if command -v docker-compose &> /dev/null; then
|
||
COMPOSE_VERSION=$(docker-compose --version | grep -oE '[0-9]+\.[0-9]+' | head -1)
|
||
log_success "Docker Compose version: $COMPOSE_VERSION"
|
||
else
|
||
COMPOSE_VERSION=$(docker compose version --short 2>/dev/null || echo "built-in")
|
||
log_success "Docker Compose version: $COMPOSE_VERSION"
|
||
fi
|
||
|
||
return 0
|
||
}
|
||
|
||
# Check port usage
|
||
check_port_availability() {
|
||
local ports="$1"
|
||
log_info "Checking port usage..."
|
||
|
||
local occupied_ports=()
|
||
|
||
IFS=',' read -ra PORT_ARRAY <<< "$ports"
|
||
for port in "${PORT_ARRAY[@]}"; do
|
||
port=$(echo "$port" | xargs) # Trim spaces
|
||
if command -v ss >/dev/null 2>&1; then
|
||
if ss -tuln | grep -q ":$port "; then
|
||
occupied_ports+=("$port")
|
||
fi
|
||
elif command -v netstat >/dev/null 2>&1; then
|
||
if netstat -tuln 2>/dev/null | grep -q ":$port "; then
|
||
occupied_ports+=("$port")
|
||
fi
|
||
else
|
||
log_warning "Unable to check port usage (missing ss and netstat)"
|
||
return 0
|
||
fi
|
||
done
|
||
|
||
if [[ ${#occupied_ports[@]} -gt 0 ]]; then
|
||
log_warning "Ports in use: ${occupied_ports[*]}"
|
||
log_info "Change ports in .env, or run './deploy.sh --clean' / 'docker-compose down' to clean old containers"
|
||
else
|
||
log_success "All ports available"
|
||
fi
|
||
}
|
||
|
||
# Detect deployment mode
|
||
detect_deployment_mode() {
|
||
log_info "Determining deployment mode..."
|
||
|
||
if [[ "$NETWORK_MODE" == "public" ]] && [[ -n "$DOMAIN_NAME" ]]; then
|
||
DEPLOYMENT_MODE="full"
|
||
log_success "Deployment mode: full (HTTPS + TURN server)"
|
||
elif [[ "$NETWORK_MODE" == "public" ]]; then
|
||
DEPLOYMENT_MODE="public"
|
||
log_success "Deployment mode: public (HTTP + TURN)"
|
||
else
|
||
DEPLOYMENT_MODE="basic"
|
||
log_success "Deployment mode: basic (intranet HTTP)"
|
||
fi
|
||
}
|
||
|
||
# Main function
|
||
main() {
|
||
echo -e "${BLUE}=== PrivyDrop Docker Environment Check ===${NC}\n"
|
||
|
||
# Parse command-line arguments
|
||
while [[ $# -gt 0 ]]; do
|
||
case $1 in
|
||
--domain)
|
||
DOMAIN_NAME="$2"
|
||
shift 2
|
||
;;
|
||
--mode)
|
||
DEPLOYMENT_MODE="$2"
|
||
case "$2" in
|
||
private|basic)
|
||
FORCED_MODE="private"
|
||
;;
|
||
public|full)
|
||
FORCED_MODE="public"
|
||
;;
|
||
*)
|
||
FORCED_MODE=""
|
||
;;
|
||
esac
|
||
shift 2
|
||
;;
|
||
--local-ip)
|
||
LOCAL_IP_OVERRIDE="$2"
|
||
shift 2
|
||
;;
|
||
*)
|
||
shift
|
||
;;
|
||
esac
|
||
done
|
||
|
||
# Run checks
|
||
detect_network_environment
|
||
echo ""
|
||
|
||
if ! check_system_resources; then
|
||
log_error "System resource check failed; resolve resource issues and retry"
|
||
exit 1
|
||
fi
|
||
echo ""
|
||
|
||
if ! verify_docker_installation; then
|
||
log_error "Docker environment check failed; please install and start Docker"
|
||
exit 1
|
||
fi
|
||
echo ""
|
||
|
||
check_port_availability "80,443,3002,3001,3478,5349,6379"
|
||
echo ""
|
||
|
||
detect_deployment_mode
|
||
echo ""
|
||
|
||
log_success "Environment check complete!"
|
||
echo -e "${BLUE}Results:${NC}"
|
||
echo " Network mode: $NETWORK_MODE"
|
||
echo " Local IP: $LOCAL_IP"
|
||
[[ -n "$PUBLIC_IP" ]] && echo " Public IP: $PUBLIC_IP"
|
||
echo " Deployment mode: $DEPLOYMENT_MODE"
|
||
|
||
# Export env vars for other scripts
|
||
export NETWORK_MODE
|
||
export LOCAL_IP
|
||
export PUBLIC_IP
|
||
export DEPLOYMENT_MODE
|
||
export DOMAIN_NAME
|
||
export LOCAL_IP_OVERRIDE
|
||
|
||
return 0
|
||
}
|
||
|
||
# If the script is executed directly
|
||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||
main "$@"
|
||
fi
|