Initial commit: SSL proxy / firewall appliance
Flask WebUI behind nginx reverse proxy with zone-based firewall, DHCP, WireGuard, and ACME certificate management.
This commit is contained in:
+249
@@ -0,0 +1,249 @@
|
||||
#!/usr/bin/env bash
|
||||
# Vacuum Wall - SSL Proxy Firewall Appliance Installer
|
||||
# Run as root on a fresh Debian 13 (trixie) system
|
||||
set -euo pipefail
|
||||
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
log() { echo -e "${GREEN}[OK]${NC} $*"; }
|
||||
warn() { echo -e "${YELLOW}[!!]${NC} $*"; }
|
||||
err() { echo -e "${RED}[!!]${NC} $*"; exit 1; }
|
||||
|
||||
PROJECT_DIR="/home/wall/vacuum-wall"
|
||||
USER_NAME="vacuum-wall"
|
||||
USER_HOME="/home/$USER_NAME"
|
||||
DOMAIN="${MGMT_DOMAIN:?ERROR: Set MGMT_DOMAIN env var (e.g., wall.lan)}"
|
||||
MGMT_USER="${MGMT_USER:-admin}"
|
||||
MGMT_PASS="${MGMT_PASS:?ERROR: Set MGMT_PASS env var for WebUI basic auth}"
|
||||
ACME_EMAIL="${ACME_EMAIL:?ERROR: Set ACME_EMAIL env var for Let's Encrypt}"
|
||||
|
||||
# --- Pre-flight checks ---
|
||||
[[ $EUID -eq 0 ]] || err "This script must be run as root."
|
||||
[[ -f /etc/debian_version ]] || warn "This script is designed for Debian/Ubuntu."
|
||||
|
||||
echo "============================================"
|
||||
echo " Vacuum Wall Appliance Installer"
|
||||
echo " Management domain: $DOMAIN"
|
||||
echo "============================================"
|
||||
|
||||
# --- 1. Install packages ---
|
||||
log "Installing system packages..."
|
||||
apt-get update -qq
|
||||
apt-get install -y -qq \
|
||||
firewalld \
|
||||
nginx \
|
||||
dnsmasq \
|
||||
wireguard-tools \
|
||||
python3 \
|
||||
python3-pip \
|
||||
jq \
|
||||
curl \
|
||||
iptables \
|
||||
nftables \
|
||||
apache2-utils
|
||||
|
||||
# Install acme.sh under the project user's home
|
||||
if [[ ! -d "$USER_HOME/.acme.sh" ]]; then
|
||||
log "Installing acme.sh..."
|
||||
mkdir -p "$USER_HOME"
|
||||
ACME_HOME="$USER_HOME/.acme.sh" curl -sS https://get.acme.sh | sh
|
||||
else
|
||||
log "acme.sh already installed."
|
||||
fi
|
||||
|
||||
# Setup Python venv with project dependencies
|
||||
log "Setting up Python virtual environment..."
|
||||
python3 -m venv "${PROJECT_DIR}/.venv"
|
||||
"${PROJECT_DIR}/.venv/bin/pip" install -q "${PROJECT_DIR}"
|
||||
chown -R "$USER_NAME:$USER_NAME" "${PROJECT_DIR}/.venv"
|
||||
|
||||
# --- 2. Create system user ---
|
||||
if ! id "$USER_NAME" &>/dev/null; then
|
||||
log "Creating system user $USER_NAME..."
|
||||
useradd --system --home-dir "$USER_HOME" --shell /usr/sbin/nologin "$USER_NAME"
|
||||
else
|
||||
log "User $USER_NAME already exists."
|
||||
fi
|
||||
|
||||
# --- 3. Setup directories ---
|
||||
log "Creating data directories..."
|
||||
mkdir -p "${PROJECT_DIR}/data"/{nginx/sites-enabled,dnsmasq,firewall,wireguard}
|
||||
mkdir -p "$USER_HOME/vacuum-wall"
|
||||
mkdir -p /etc/wireguard
|
||||
mkdir -p /etc/dnsmasq
|
||||
|
||||
chown -R "$USER_NAME:$USER_NAME" "${PROJECT_DIR}/data"
|
||||
chown -R "$USER_NAME:$USER_NAME" "${PROJECT_DIR}/lib"
|
||||
chown -R "$USER_NAME:$USER_NAME" "${PROJECT_DIR}/webui"
|
||||
chown -R "$USER_NAME:$USER_NAME" "$USER_HOME/vacuum-wall"
|
||||
|
||||
# --- 4. Install sudoers ---
|
||||
log "Installing sudoers whitelist..."
|
||||
install -m 0440 "${PROJECT_DIR}/system/sudoers.d/vacuum-wall" /etc/sudoers.d/vacuum-wall
|
||||
|
||||
# Validate sudoers syntax
|
||||
visudo -cf /etc/sudoers.d/vacuum-wall || err "Invalid sudoers file!"
|
||||
|
||||
# --- 5. Enable IP forwarding ---
|
||||
log "Enabling IP forwarding..."
|
||||
if ! grep -q "^net.ipv4.ip_forward=1" /etc/sysctl.conf 2>/dev/null; then
|
||||
echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf
|
||||
fi
|
||||
sysctl -w net.ipv4.ip_forward=1 2>/dev/null || warn "Could not enable IP forwarding (may need kernel access)"
|
||||
|
||||
# --- 6. Start and configure firewalld ---
|
||||
log "Enabling firewalld..."
|
||||
systemctl enable firewalld || warn "Could not enable firewalld (already running?)"
|
||||
systemctl start firewalld || warn "Could not start firewalld (may need D-Bus)"
|
||||
|
||||
# Allow management access (HTTP/HTTPS for the proxy)
|
||||
firewall-cmd --permanent --add-service=http 2>/dev/null || true
|
||||
firewall-cmd --permanent --add-service=https 2>/dev/null || true
|
||||
firewall-cmd --permanent --add-service=ssh 2>/dev/null || true
|
||||
firewall-cmd --reload 2>/dev/null || true
|
||||
|
||||
# --- 7. Configure dnsmasq ---
|
||||
log "Configuring dnsmasq..."
|
||||
systemctl enable dnsmasq || true
|
||||
systemctl start dnsmasq || warn "Could not start dnsmasq (no interfaces configured yet)"
|
||||
|
||||
# --- 8. Setup nginx management proxy ---
|
||||
log "Generating self-signed certificate for management domain..."
|
||||
mkdir -p "$USER_HOME/.acme.sh/$DOMAIN"
|
||||
|
||||
openssl req -x509 -nodes -days 365 -newkey rsa:2048 \
|
||||
-keyout "$USER_HOME/.acme.sh/$DOMAIN/$DOMAIN.key" \
|
||||
-out "$USER_HOME/.acme.sh/$DOMAIN/fullchain.cer" \
|
||||
-subj "/CN=$DOMAIN" \
|
||||
-addext "subjectAltName=DNS:$DOMAIN"
|
||||
|
||||
chown -R "$USER_NAME:$USER_NAME" "$USER_HOME/.acme.sh"
|
||||
|
||||
# Generate htpasswd (credentials passed via environment to avoid shell injection)
|
||||
mkdir -p "$USER_HOME/vacuum-wall"
|
||||
htpasswd -cb "$USER_HOME/vacuum-wall/.htpasswd" "$MGMT_USER" "$MGMT_PASS" 2>/dev/null || \
|
||||
MGMT_USER="$MGMT_USER" MGMT_PASS="$MGMT_PASS" python3 -c "
|
||||
import os, crypt, base64
|
||||
password = os.environ['MGMT_PASS']
|
||||
user = os.environ['MGMT_USER']
|
||||
salt = '\$6\$' + base64.b64encode(os.urandom(16)).decode().rstrip('=')[:16]
|
||||
hashed = crypt.crypt(password, salt)
|
||||
with open('$USER_HOME/vacuum-wall/.htpasswd', 'w') as f:
|
||||
f.write(user + ':' + hashed + '\n')
|
||||
" 2>/dev/null || \
|
||||
warn "Could not generate htpasswd (install apache2-utils or python3-crypt)"
|
||||
|
||||
chown "$USER_NAME:$USER_NAME" "$USER_HOME/vacuum-wall/.htpasswd" 2>/dev/null
|
||||
|
||||
# Write WebSocket upgrade map (nginx conf.d/ is already inside http {} context)
|
||||
cat > /etc/nginx/conf.d/vacuum-wall-map.conf <<'MAPEOF'
|
||||
# Vacuum Wall - WebSocket upgrade map
|
||||
map $http_upgrade $connection_upgrade {
|
||||
default upgrade;
|
||||
'' close;
|
||||
}
|
||||
MAPEOF
|
||||
|
||||
# Write the management site block (conf.d/ is inside http {}, no extra http {} needed)
|
||||
cat > /etc/nginx/conf.d/vacuum-wall-mgmt.conf <<MGMTSITEEOF
|
||||
# Vacuum Wall - Management Proxy
|
||||
# Auto-generated by install.sh
|
||||
|
||||
server {
|
||||
listen 80;
|
||||
server_name $DOMAIN;
|
||||
return 301 https://\$host\$request_uri;
|
||||
}
|
||||
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name $DOMAIN;
|
||||
|
||||
ssl_certificate $USER_HOME/.acme.sh/$DOMAIN/fullchain.cer;
|
||||
ssl_certificate_key $USER_HOME/.acme.sh/$DOMAIN/$DOMAIN.key;
|
||||
|
||||
ssl_protocols TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
auth_basic "Vacuum Wall";
|
||||
auth_basic_user_file $USER_HOME/vacuum-wall/.htpasswd;
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:9090;
|
||||
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;
|
||||
|
||||
# WebSocket support
|
||||
proxy_http_version 1.1;
|
||||
proxy_set_header Upgrade \$http_upgrade;
|
||||
proxy_set_header Connection \$connection_upgrade;
|
||||
}
|
||||
}
|
||||
MGMTSITEEOF
|
||||
|
||||
# --- 9. Install systemd units ---
|
||||
log "Installing systemd units..."
|
||||
install -m 0644 "${PROJECT_DIR}/system/systemd/vacuum-wall.service" /etc/systemd/system/vacuum-wall.service
|
||||
install -m 0644 "${PROJECT_DIR}/system/systemd/vacuum-wall-acme.service" /etc/systemd/system/vacuum-wall-acme.service
|
||||
install -m 0644 "${PROJECT_DIR}/system/systemd/vacuum-wall-acme.timer" /etc/systemd/system/vacuum-wall-acme.timer
|
||||
systemctl daemon-reload
|
||||
|
||||
# --- 10. Setup initial firewalld zones ---
|
||||
log "Setting up default firewalld zones..."
|
||||
|
||||
# Internal zone (LAN)
|
||||
firewall-cmd --permanent --new-zone=internal 2>/dev/null || true
|
||||
firewall-cmd --permanent --zone=internal --set-target=ACCEPT 2>/dev/null || true
|
||||
firewall-cmd --permanent --zone=internal --add-service=dhcp 2>/dev/null || true
|
||||
firewall-cmd --permanent --zone=internal --add-service=dns 2>/dev/null || true
|
||||
firewall-cmd --permanent --zone=internal --add-service=ntp 2>/dev/null || true
|
||||
|
||||
# VPN zone
|
||||
firewall-cmd --permanent --new-zone=vpn 2>/dev/null || true
|
||||
firewall-cmd --permanent --zone=vpn --set-target=ACCEPT 2>/dev/null || true
|
||||
|
||||
# Public/external zone defaults are fine
|
||||
|
||||
firewall-cmd --reload 2>/dev/null || true
|
||||
|
||||
# --- 11. Enable and start services ---
|
||||
log "Enabling services..."
|
||||
systemctl enable nginx
|
||||
systemctl enable vacuum-wall
|
||||
systemctl enable vacuum-wall-acme.timer
|
||||
|
||||
systemctl start nginx 2>/dev/null || warn "Could not start nginx (check config)"
|
||||
systemctl start vacuum-wall 2>/dev/null || warn "Could not start vacuum-wall WebUI"
|
||||
|
||||
# --- 12. Configure acme.sh default email ---
|
||||
log "Configuring acme.sh default email..."
|
||||
"$USER_HOME/.acme.sh/acme.sh" --register-account -m "$ACME_EMAIL" 2>/dev/null || \
|
||||
warn "Could not register acme.sh account (will be done from WebUI)"
|
||||
|
||||
# --- Done ---
|
||||
echo ""
|
||||
echo "============================================"
|
||||
echo -e " ${GREEN}Vacuum Wall installed successfully!${NC}"
|
||||
echo "============================================"
|
||||
echo ""
|
||||
echo " Management UI: https://$DOMAIN"
|
||||
echo " User: $MGMT_USER"
|
||||
echo " WebUI service: vacuum-wall.service"
|
||||
echo " ACME renewal: vacuum-wall-acme.timer"
|
||||
echo ""
|
||||
echo " Next steps:"
|
||||
echo " 1. Assign interfaces to zones from the WebUI"
|
||||
echo " 2. Configure DHCP ranges for your LAN"
|
||||
echo " 3. Add proxy domains with Let's Encrypt certs"
|
||||
echo " 4. Set up WireGuard tunnel (optional)"
|
||||
echo ""
|
||||
echo " NOTE: A self-signed certificate was generated."
|
||||
echo " From the WebUI, issue a real certificate for $DOMAIN"
|
||||
echo " when DNS points to this appliance."
|
||||
echo ""
|
||||
Reference in New Issue
Block a user