|
#!/usr/bin/env bash |
|
|
|
# Proxmox Dante SOCKS5 Proxy LXC Setup Script |
|
# Creates a minimal LXC container with Dante SOCKS5 proxy for routing traffic |
|
# through a residential IP (useful for yt-dlp, avoiding datacenter IP blocks) |
|
# |
|
# Architecture: |
|
# Fly.io → Tailscale mesh → Exit Node LXC → Dante LXC (LAN) → Internet |
|
# |
|
# The Dante LXC sits on your home LAN (no Tailscale needed). Fly.io connects |
|
# through your existing Tailscale exit node, which can reach the Dante LXC |
|
# on the local network. Traffic exits via your home router's residential IP. |
|
# |
|
# Usage: Run on Proxmox host as root |
|
# bash -c "$(curl -fsSL https://gist.githubusercontent.com/.../proxmox-dante-setup.sh)" |
|
# |
|
# Inspired by tteck's Proxmox helper scripts |
|
|
|
set -euo pipefail |
|
|
|
# ============================================================================= |
|
# CONFIGURATION DEFAULTS |
|
# ============================================================================= |
|
|
|
DEFAULT_CTID="$(pvesh get /cluster/nextid 2>/dev/null || echo 100)" |
|
DEFAULT_HOSTNAME="dante-proxy" |
|
DEFAULT_MEMORY="512" |
|
DEFAULT_DISK="4" |
|
DEFAULT_CORES="1" |
|
DEFAULT_STORAGE="local-lvm" |
|
DEFAULT_TEMPLATE="debian-12-standard_12.2-1_amd64.tar.zst" |
|
DEFAULT_BRIDGE="vmbr0" |
|
DEFAULT_DANTE_PORT="1080" |
|
|
|
# ============================================================================= |
|
# COLORS AND OUTPUT HELPERS |
|
# ============================================================================= |
|
|
|
RD=$(echo "\033[01;31m") |
|
GN=$(echo "\033[1;92m") |
|
YW=$(echo "\033[33m") |
|
BL=$(echo "\033[36m") |
|
CL=$(echo "\033[m") |
|
CM="${GN}✓${CL}" |
|
CROSS="${RD}✗${CL}" |
|
SPINNER="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏" |
|
|
|
header_info() { |
|
clear |
|
cat <<"EOF" |
|
____ __ ____ |
|
/ __ \____ _____ / /____ / __ \_________ _ ____ __ |
|
/ / / / __ `/ __ \/ __/ _ \ / /_/ / ___/ __ \| |/_/ / / / |
|
/ /_/ / /_/ / / / / /_/ __/ / ____/ / / /_/ /> </ /_/ / |
|
/_____/\__,_/_/ /_/\__/\___/ /_/ /_/ \____/_/|_|\__, / |
|
/____/ |
|
SOCKS5 Proxy for Proxmox LXC |
|
EOF |
|
echo |
|
} |
|
|
|
msg_info() { |
|
local msg="$1" |
|
echo -ne " ${BL}${SPINNER:0:1}${CL} ${msg}..." |
|
} |
|
|
|
msg_ok() { |
|
local msg="$1" |
|
echo -e "\r ${CM} ${msg} " |
|
} |
|
|
|
msg_error() { |
|
local msg="$1" |
|
echo -e "\r ${CROSS} ${msg} " |
|
} |
|
|
|
msg_warn() { |
|
local msg="$1" |
|
echo -e " ${YW}⚠${CL} ${msg}" |
|
} |
|
|
|
# ============================================================================= |
|
# VALIDATION FUNCTIONS |
|
# ============================================================================= |
|
|
|
check_root() { |
|
if [[ $EUID -ne 0 ]]; then |
|
msg_error "This script must be run as root on the Proxmox host" |
|
exit 1 |
|
fi |
|
} |
|
|
|
check_proxmox() { |
|
if ! command -v pct &>/dev/null; then |
|
msg_error "This script must be run on a Proxmox VE host" |
|
exit 1 |
|
fi |
|
} |
|
|
|
check_template() { |
|
local storage="$1" |
|
local template="$2" |
|
|
|
if ! pveam list "$storage" 2>/dev/null | grep -q "$template"; then |
|
msg_info "Downloading Debian 12 template" |
|
pveam download "$storage" "$template" &>/dev/null |
|
msg_ok "Downloaded Debian 12 template" |
|
fi |
|
} |
|
|
|
validate_ctid() { |
|
local ctid="$1" |
|
if [[ ! "$ctid" =~ ^[0-9]+$ ]] || [[ "$ctid" -lt 100 ]]; then |
|
msg_error "Invalid container ID: $ctid" |
|
return 1 |
|
fi |
|
if pct status "$ctid" &>/dev/null; then |
|
msg_error "Container ID $ctid already exists" |
|
return 1 |
|
fi |
|
return 0 |
|
} |
|
|
|
# ============================================================================= |
|
# INTERACTIVE PROMPTS |
|
# ============================================================================= |
|
|
|
prompt_settings() { |
|
echo -e "${BL}Container Settings${CL}" |
|
echo -e "${YW}─────────────────────────────────────────${CL}" |
|
|
|
# Container ID |
|
while true; do |
|
read -rp " Container ID [$DEFAULT_CTID]: " CTID |
|
CTID="${CTID:-$DEFAULT_CTID}" |
|
validate_ctid "$CTID" && break |
|
done |
|
|
|
# Hostname |
|
read -rp " Hostname [$DEFAULT_HOSTNAME]: " HOSTNAME |
|
HOSTNAME="${HOSTNAME:-$DEFAULT_HOSTNAME}" |
|
|
|
# Memory |
|
read -rp " Memory in MB [$DEFAULT_MEMORY]: " MEMORY |
|
MEMORY="${MEMORY:-$DEFAULT_MEMORY}" |
|
|
|
# Disk |
|
read -rp " Disk size in GB [$DEFAULT_DISK]: " DISK |
|
DISK="${DISK:-$DEFAULT_DISK}" |
|
|
|
# Storage |
|
read -rp " Storage [$DEFAULT_STORAGE]: " STORAGE |
|
STORAGE="${STORAGE:-$DEFAULT_STORAGE}" |
|
|
|
# Bridge |
|
read -rp " Network bridge [$DEFAULT_BRIDGE]: " BRIDGE |
|
BRIDGE="${BRIDGE:-$DEFAULT_BRIDGE}" |
|
|
|
echo |
|
echo |
|
echo -e "${BL}Dante Proxy Settings${CL}" |
|
echo -e "${YW}─────────────────────────────────────────${CL}" |
|
|
|
# Dante port |
|
read -rp " SOCKS5 port [$DEFAULT_DANTE_PORT]: " DANTE_PORT |
|
DANTE_PORT="${DANTE_PORT:-$DEFAULT_DANTE_PORT}" |
|
|
|
echo |
|
} |
|
|
|
confirm_settings() { |
|
echo -e "${BL}Configuration Summary${CL}" |
|
echo -e "${YW}─────────────────────────────────────────${CL}" |
|
echo -e " Container ID: ${GN}$CTID${CL}" |
|
echo -e " Hostname: ${GN}$HOSTNAME${CL}" |
|
echo -e " Memory: ${GN}${MEMORY}MB${CL}" |
|
echo -e " Disk: ${GN}${DISK}GB${CL}" |
|
echo -e " Storage: ${GN}$STORAGE${CL}" |
|
echo -e " Bridge: ${GN}$BRIDGE${CL}" |
|
echo -e " SOCKS5 Port: ${GN}$DANTE_PORT${CL}" |
|
echo |
|
|
|
read -rp "Proceed with installation? [y/N]: " confirm |
|
if [[ ! "$confirm" =~ ^[Yy]$ ]]; then |
|
echo "Installation cancelled." |
|
exit 0 |
|
fi |
|
} |
|
|
|
# ============================================================================= |
|
# LXC CREATION |
|
# ============================================================================= |
|
|
|
create_lxc() { |
|
msg_info "Creating LXC container $CTID" |
|
|
|
# Check/download template |
|
local template_storage="${STORAGE%%:*}" |
|
[[ "$template_storage" == "$STORAGE" ]] && template_storage="local" |
|
check_template "$template_storage" "$DEFAULT_TEMPLATE" |
|
|
|
# Create container |
|
pct create "$CTID" "${template_storage}:vztmpl/${DEFAULT_TEMPLATE}" \ |
|
--hostname "$HOSTNAME" \ |
|
--memory "$MEMORY" \ |
|
--cores "$DEFAULT_CORES" \ |
|
--rootfs "${STORAGE}:${DISK}" \ |
|
--net0 "name=eth0,bridge=${BRIDGE},ip=dhcp" \ |
|
--unprivileged 1 \ |
|
--features "nesting=1" \ |
|
--onboot 1 \ |
|
--start 0 \ |
|
&>/dev/null |
|
|
|
msg_ok "Created LXC container $CTID" |
|
} |
|
|
|
start_lxc() { |
|
msg_info "Starting LXC container" |
|
pct start "$CTID" &>/dev/null |
|
sleep 3 |
|
|
|
# Wait for network |
|
local retries=0 |
|
while ! pct exec "$CTID" -- ping -c 1 8.8.8.8 &>/dev/null; do |
|
sleep 1 |
|
((retries++)) |
|
if [[ $retries -gt 30 ]]; then |
|
msg_error "Container failed to get network connectivity" |
|
exit 1 |
|
fi |
|
done |
|
|
|
msg_ok "Started LXC container" |
|
} |
|
|
|
# ============================================================================= |
|
# SOFTWARE INSTALLATION |
|
# ============================================================================= |
|
|
|
install_packages() { |
|
msg_info "Updating system and installing packages" |
|
|
|
pct exec "$CTID" -- bash -c " |
|
apt-get update &>/dev/null |
|
apt-get install -y dante-server &>/dev/null |
|
" |
|
|
|
msg_ok "Installed Dante" |
|
} |
|
|
|
configure_dante() { |
|
msg_info "Configuring Dante SOCKS5 proxy" |
|
|
|
# Get the LAN interface and IP |
|
local lan_iface |
|
local lan_ip |
|
lan_iface=$(pct exec "$CTID" -- ip route get 8.8.8.8 | grep -oP 'dev \K\S+' | head -1) |
|
lan_ip=$(pct exec "$CTID" -- ip -4 addr show "$lan_iface" | grep -oP 'inet \K[\d.]+' | head -1) |
|
|
|
if [[ -z "$lan_ip" ]]; then |
|
msg_error "Could not determine LAN IP" |
|
exit 1 |
|
fi |
|
|
|
# Create config file on Proxmox host, then push to container |
|
# This avoids all the heredoc/quoting issues with pct exec |
|
local tmp_config |
|
tmp_config=$(mktemp) |
|
|
|
cat > "$tmp_config" << EOF |
|
# Dante SOCKS5 Proxy Configuration |
|
# Generated by proxmox-dante-setup.sh |
|
|
|
logoutput: syslog |
|
|
|
internal: ${lan_iface} port = ${DANTE_PORT} |
|
external: ${lan_iface} |
|
|
|
socksmethod: none |
|
|
|
# Client rules - allow private networks and Tailscale |
|
client pass { from: 10.0.0.0/8 to: 0.0.0.0/0 } |
|
client pass { from: 172.16.0.0/12 to: 0.0.0.0/0 } |
|
client pass { from: 192.168.0.0/16 to: 0.0.0.0/0 } |
|
client pass { from: 100.64.0.0/10 to: 0.0.0.0/0 } |
|
client block { from: 0.0.0.0/0 to: 0.0.0.0/0 } |
|
|
|
# SOCKS rules - allow all destinations |
|
socks pass { from: 0.0.0.0/0 to: 0.0.0.0/0 } |
|
EOF |
|
|
|
# Push config file into container |
|
pct push "$CTID" "$tmp_config" /etc/danted.conf |
|
rm -f "$tmp_config" |
|
|
|
# Enable and start Dante |
|
pct exec "$CTID" -- systemctl enable danted &>/dev/null |
|
pct exec "$CTID" -- systemctl restart danted |
|
|
|
# Store LAN IP for later |
|
LAN_IP="$lan_ip" |
|
|
|
msg_ok "Configured Dante (${lan_iface}: ${lan_ip}:${DANTE_PORT})" |
|
} |
|
|
|
# ============================================================================= |
|
# VERIFICATION |
|
# ============================================================================= |
|
|
|
verify_setup() { |
|
msg_info "Verifying setup" |
|
|
|
# Check Dante is listening |
|
if ! pct exec "$CTID" -- ss -tlnp | grep -q ":${DANTE_PORT}"; then |
|
msg_error "Dante is not listening on port $DANTE_PORT" |
|
echo |
|
echo "Debug info:" |
|
pct exec "$CTID" -- systemctl status danted |
|
exit 1 |
|
fi |
|
|
|
# Test proxy with external request |
|
local public_ip |
|
public_ip=$(pct exec "$CTID" -- curl -s -x "socks5://127.0.0.1:${DANTE_PORT}" https://ifconfig.me 2>/dev/null || echo "failed") |
|
|
|
if [[ "$public_ip" == "failed" ]]; then |
|
msg_warn "Could not verify proxy (may still work)" |
|
else |
|
msg_ok "Verified setup (Public IP: $public_ip)" |
|
fi |
|
} |
|
|
|
print_summary() { |
|
echo |
|
echo -e "${GN}═══════════════════════════════════════════════════════════════${CL}" |
|
echo -e "${GN} SETUP COMPLETE! ${CL}" |
|
echo -e "${GN}═══════════════════════════════════════════════════════════════${CL}" |
|
echo |
|
echo -e " ${BL}Container:${CL} $CTID ($HOSTNAME)" |
|
echo -e " ${BL}LAN IP:${CL} $LAN_IP" |
|
echo -e " ${BL}SOCKS5 Proxy:${CL} socks5://${LAN_IP}:${DANTE_PORT}" |
|
echo |
|
echo -e " ${YW}To use with yt-dlp on Fly.io:${CL}" |
|
echo -e " --proxy socks5://${LAN_IP}:${DANTE_PORT}" |
|
echo |
|
echo -e " ${YW}Or set environment variable:${CL}" |
|
echo -e " YTDLP_PROXY=socks5://${LAN_IP}:${DANTE_PORT}" |
|
echo |
|
echo -e " ${YW}Test from your Tailscale exit node:${CL}" |
|
echo -e " curl -x socks5://${LAN_IP}:${DANTE_PORT} https://ifconfig.me" |
|
echo |
|
echo -e " ${YW}Note:${CL} Fly.io connects through your Tailscale exit node," |
|
echo -e " which can reach this proxy on your LAN." |
|
echo |
|
echo -e "${GN}═══════════════════════════════════════════════════════════════${CL}" |
|
} |
|
|
|
# ============================================================================= |
|
# MAIN |
|
# ============================================================================= |
|
|
|
main() { |
|
header_info |
|
check_root |
|
check_proxmox |
|
|
|
prompt_settings |
|
confirm_settings |
|
|
|
echo |
|
create_lxc |
|
start_lxc |
|
install_packages |
|
configure_dante |
|
verify_setup |
|
|
|
print_summary |
|
} |
|
|
|
main "$@" |