Skip to content

Instantly share code, notes, and snippets.

@softdream1981
Created August 2, 2025 10:03
Show Gist options
  • Select an option

  • Save softdream1981/0bb394749c0c4f0e2473b7735255736d to your computer and use it in GitHub Desktop.

Select an option

Save softdream1981/0bb394749c0c4f0e2473b7735255736d to your computer and use it in GitHub Desktop.
captive
#!/bin/bash
# Captive Portal Setup Script for Raspberry Pi 3
# Using TL-WN722N v1.10 (wlan1)
# Run with: sudo bash setup_captive_portal.sh
set -e # Exit on error
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Function to print colored output
print_status() {
echo -e "${GREEN}[+]${NC} $1"
}
print_error() {
echo -e "${RED}[!]${NC} $1"
}
print_warning() {
echo -e "${YELLOW}[*]${NC} $1"
}
# Check if running as root
if [ "$EUID" -ne 0 ]; then
print_error "Please run as root (use sudo)"
exit 1
fi
print_status "Starting Captive Portal Setup..."
# Update system and install required packages
print_status "Updating system and installing required packages..."
apt update
apt install -y hostapd dnsmasq nginx php-fpm iptables-persistent
# Stop services during configuration
print_status "Stopping services..."
systemctl stop hostapd 2>/dev/null || true
systemctl stop dnsmasq 2>/dev/null || true
systemctl stop nginx 2>/dev/null || true
# Configure network interface
print_status "Configuring network interface..."
if ! grep -q "interface wlan1" /etc/dhcpcd.conf; then
cat >> /etc/dhcpcd.conf << 'EOF'
interface wlan1
static ip_address=192.168.4.1/24
nohook wpa_supplicant
EOF
fi
# Configure hostapd
print_status "Configuring hostapd..."
cat > /etc/hostapd/hostapd.conf << 'EOF'
interface=wlan1
driver=nl80211
ssid=GuestWiFi
hw_mode=g
channel=7
wmm_enabled=0
macaddr_acl=0
auth_algs=1
ignore_broadcast_ssid=0
EOF
# Update hostapd default config
sed -i 's/#DAEMON_CONF=""/DAEMON_CONF="\/etc\/hostapd\/hostapd.conf"/' /etc/default/hostapd
# Configure dnsmasq
print_status "Configuring dnsmasq..."
mv /etc/dnsmasq.conf /etc/dnsmasq.conf.orig 2>/dev/null || true
cat > /etc/dnsmasq.conf << 'EOF'
interface=wlan1
dhcp-range=192.168.4.2,192.168.4.20,255.255.255.0,24h
domain=wlan
address=/#/192.168.4.1
EOF
# Create web portal directory
print_status "Creating web portal..."
mkdir -p /var/www/portal
chown -R www-data:www-data /var/www/portal
# Create index.php
cat > /var/www/portal/index.php << 'EOF'
<?php
session_start();
// Check if user is already authenticated
if (isset($_SESSION['authenticated']) && $_SESSION['authenticated'] === true) {
header("Location: /success.php");
exit();
}
// Handle form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$name = $_POST['name'] ?? '';
$email = $_POST['email'] ?? '';
$agree = $_POST['agree'] ?? '';
if (!empty($name) && !empty($email) && $agree === 'yes') {
$_SESSION['authenticated'] = true;
$_SESSION['mac'] = $_SERVER['REMOTE_ADDR'];
// Log user info
$log_entry = date('Y-m-d H:i:s') . " - Name: $name, Email: $email, IP: " . $_SERVER['REMOTE_ADDR'] . "\n";
file_put_contents('/var/log/portal_users.log', $log_entry, FILE_APPEND);
// Add firewall rule to allow this user
$mac = trim(shell_exec("arp -n " . $_SERVER['REMOTE_ADDR'] . " | grep -o '[0-9A-Fa-f]\{2\}:[0-9A-Fa-f]\{2\}:[0-9A-Fa-f]\{2\}:[0-9A-Fa-f]\{2\}:[0-9A-Fa-f]\{2\}:[0-9A-Fa-f]\{2\}'"));
if (!empty($mac)) {
shell_exec("sudo /usr/local/bin/portal_allow.sh " . escapeshellarg($mac));
}
header("Location: /success.php");
exit();
} else {
$error = "Please fill in all fields and agree to the terms.";
}
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Guest WiFi Portal</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background-color: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
max-width: 400px;
width: 100%;
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
.form-group {
margin-bottom: 20px;
}
label {
display: block;
margin-bottom: 5px;
color: #666;
font-weight: bold;
}
input[type="text"],
input[type="email"] {
width: 100%;
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
box-sizing: border-box;
}
.checkbox-group {
display: flex;
align-items: center;
margin: 20px 0;
}
input[type="checkbox"] {
margin-right: 10px;
}
button {
width: 100%;
padding: 12px;
background-color: #007bff;
color: white;
border: none;
border-radius: 5px;
font-size: 16px;
cursor: pointer;
transition: background-color 0.3s;
}
button:hover {
background-color: #0056b3;
}
.error {
color: #dc3545;
text-align: center;
margin-bottom: 20px;
}
.terms {
font-size: 12px;
color: #666;
margin-top: 20px;
padding: 10px;
background-color: #f8f9fa;
border-radius: 5px;
}
</style>
</head>
<body>
<div class="container">
<h1>Welcome to Guest WiFi</h1>
<?php if (isset($error)): ?>
<div class="error"><?php echo htmlspecialchars($error); ?></div>
<?php endif; ?>
<form method="POST">
<div class="form-group">
<label for="name">Name:</label>
<input type="text" id="name" name="name" required>
</div>
<div class="form-group">
<label for="email">Email:</label>
<input type="email" id="email" name="email" required>
</div>
<div class="checkbox-group">
<input type="checkbox" id="agree" name="agree" value="yes" required>
<label for="agree">I agree to the terms and conditions</label>
</div>
<button type="submit">Connect to Internet</button>
<div class="terms">
<strong>Terms of Use:</strong> By connecting to this network, you agree to use it responsibly and legally. We reserve the right to monitor and restrict access as needed.
</div>
</form>
</div>
</body>
</html>
EOF
# Create success.php
cat > /var/www/portal/success.php << 'EOF'
<?php
session_start();
if (!isset($_SESSION['authenticated']) || $_SESSION['authenticated'] !== true) {
header("Location: /");
exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Connected!</title>
<style>
body {
font-family: Arial, sans-serif;
background-color: #f0f0f0;
margin: 0;
padding: 0;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
}
.container {
background-color: white;
padding: 40px;
border-radius: 10px;
box-shadow: 0 0 20px rgba(0,0,0,0.1);
text-align: center;
max-width: 400px;
}
h1 {
color: #28a745;
margin-bottom: 20px;
}
p {
color: #666;
line-height: 1.6;
}
.icon {
font-size: 60px;
color: #28a745;
margin-bottom: 20px;
}
</style>
</head>
<body>
<div class="container">
<div class="icon">✓</div>
<h1>Successfully Connected!</h1>
<p>You now have internet access. Enjoy browsing!</p>
<p>This window will redirect in 5 seconds...</p>
</div>
<script>
setTimeout(function() {
window.location.href = 'http://google.com';
}, 5000);
</script>
</body>
</html>
EOF
# Set proper permissions for web files
chown -R www-data:www-data /var/www/portal
# Configure nginx
print_status "Configuring nginx..."
cat > /etc/nginx/sites-available/portal << 'EOF'
server {
listen 80 default_server;
listen [::]:80 default_server;
root /var/www/portal;
index index.php index.html;
server_name _;
# Catch all requests
location / {
try_files $uri $uri/ /index.php?$args;
}
location ~ \.php$ {
include snippets/fastcgi-php.conf;
fastcgi_pass unix:/var/run/php/php7.4-fpm.sock;
}
# Redirect all 404s to portal
error_page 404 /index.php;
}
EOF
# Enable nginx site
ln -sf /etc/nginx/sites-available/portal /etc/nginx/sites-enabled/
rm -f /etc/nginx/sites-enabled/default
# Create firewall allow script
print_status "Creating firewall scripts..."
cat > /usr/local/bin/portal_allow.sh << 'EOF'
#!/bin/bash
MAC=$1
if [ -z "$MAC" ]; then
echo "Usage: $0 <MAC_ADDRESS>"
exit 1
fi
# Add iptables rule to allow this MAC address
iptables -t mangle -I PREROUTING -m mac --mac-source $MAC -j RETURN
EOF
chmod +x /usr/local/bin/portal_allow.sh
# Create iptables setup script
cat > /usr/local/bin/setup_portal_iptables.sh << 'EOF'
#!/bin/bash
# Clear existing rules
iptables -t nat -F
iptables -t mangle -F
# Enable IP forwarding
echo 1 > /proc/sys/net/ipv4/ip_forward
# Allow established connections
iptables -A FORWARD -m state --state ESTABLISHED,RELATED -j ACCEPT
# Mark packets from unauthorized users
iptables -t mangle -N captive_portal 2>/dev/null || true
iptables -t mangle -F captive_portal 2>/dev/null || true
iptables -t mangle -A PREROUTING -i wlan1 -j captive_portal
# Allow DHCP and DNS
iptables -t mangle -A captive_portal -p udp --dport 53 -j RETURN
iptables -t mangle -A captive_portal -p udp --dport 67:68 -j RETURN
# Allow access to portal server
iptables -t mangle -A captive_portal -d 192.168.4.1 -j RETURN
# Mark unauthorized packets
iptables -t mangle -A captive_portal -j MARK --set-mark 99
# Redirect HTTP traffic to portal
iptables -t nat -A PREROUTING -m mark --mark 99 -p tcp --dport 80 -j DNAT --to-destination 192.168.4.1:80
iptables -t nat -A PREROUTING -m mark --mark 99 -p tcp --dport 443 -j DNAT --to-destination 192.168.4.1:80
# Drop all other marked packets
iptables -A FORWARD -m mark --mark 99 -j DROP
# Allow forwarding from wlan1 to eth0
iptables -A FORWARD -i wlan1 -o eth0 -m state --state NEW -j ACCEPT
# NAT for internet access
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
EOF
chmod +x /usr/local/bin/setup_portal_iptables.sh
# Create systemd service
print_status "Creating systemd service..."
cat > /etc/systemd/system/captive-portal.service << 'EOF'
[Unit]
Description=Captive Portal IPTables Rules
After=network.target
[Service]
Type=oneshot
ExecStart=/usr/local/bin/setup_portal_iptables.sh
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
# Configure sudoers for PHP
print_status "Configuring sudoers..."
echo "www-data ALL=(ALL) NOPASSWD: /usr/local/bin/portal_allow.sh" > /etc/sudoers.d/captive-portal
chmod 440 /etc/sudoers.d/captive-portal
# Create log file
touch /var/log/portal_users.log
chown www-data:www-data /var/log/portal_users.log
# Enable IP forwarding permanently
print_status "Enabling IP forwarding..."
sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/' /etc/sysctl.conf
sysctl -p
# Enable services
print_status "Enabling services..."
systemctl daemon-reload
systemctl unmask hostapd
systemctl enable hostapd
systemctl enable dnsmasq
systemctl enable nginx
systemctl enable captive-portal
# Save iptables rules
print_status "Saving iptables rules..."
iptables-save > /etc/iptables/rules.v4
print_status "Setup complete!"
print_warning "The system will reboot in 10 seconds..."
print_warning "After reboot:"
print_warning "1. Connect to 'GuestWiFi' network"
print_warning "2. Open any website to see the portal"
print_warning "3. Monitor users: tail -f /var/log/portal_users.log"
# Optional: Create uninstall script
cat > /usr/local/bin/uninstall_captive_portal.sh << 'EOF'
#!/bin/bash
# Uninstall script for captive portal
echo "Removing captive portal..."
# Stop services
systemctl stop hostapd
systemctl stop dnsmasq
systemctl stop captive-portal
systemctl disable hostapd
systemctl disable dnsmasq
systemctl disable captive-portal
# Remove files
rm -f /etc/hostapd/hostapd.conf
rm -f /etc/nginx/sites-enabled/portal
rm -f /etc/nginx/sites-available/portal
rm -rf /var/www/portal
rm -f /usr/local/bin/portal_allow.sh
rm -f /usr/local/bin/setup_portal_iptables.sh
rm -f /etc/systemd/system/captive-portal.service
rm -f /etc/sudoers.d/captive-portal
rm -f /var/log/portal_users.log
# Restore original dnsmasq config
mv /etc/dnsmasq.conf.orig /etc/dnsmasq.conf 2>/dev/null || true
# Clear iptables rules
iptables -t nat -F
iptables -t mangle -F
echo "Captive portal removed. Please reboot."
EOF
chmod +x /usr/local/bin/uninstall_captive_portal.sh
sleep 10
reboot
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment