wip: wireguard
This commit is contained in:
+43
-1
@@ -832,6 +832,44 @@ def update_peer_ip():
|
||||
logger.error(f"Error updating peer IP: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/wireguard/peers/status', methods=['POST'])
|
||||
def get_peer_status():
|
||||
"""Get WireGuard peer status."""
|
||||
try:
|
||||
data = request.get_json(silent=True)
|
||||
if data is None or 'public_key' not in data:
|
||||
return jsonify({"error": "Missing public key"}), 400
|
||||
|
||||
public_key = data['public_key']
|
||||
status = wireguard_manager.get_peer_status(public_key)
|
||||
return jsonify(status)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting peer status: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/wireguard/network/setup', methods=['POST'])
|
||||
def setup_network():
|
||||
"""Setup network configuration for internet access."""
|
||||
try:
|
||||
success = wireguard_manager.setup_network_configuration()
|
||||
if success:
|
||||
return jsonify({"message": "Network configuration setup completed successfully"})
|
||||
else:
|
||||
return jsonify({"error": "Failed to setup network configuration"}), 500
|
||||
except Exception as e:
|
||||
logger.error(f"Error setting up network configuration: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/wireguard/network/status', methods=['GET'])
|
||||
def get_network_status():
|
||||
"""Get network configuration status."""
|
||||
try:
|
||||
status = wireguard_manager.get_network_status()
|
||||
return jsonify(status)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting network status: {e}")
|
||||
return jsonify({"error": str(e)}), 500
|
||||
|
||||
@app.route('/api/wireguard/peers/config', methods=['POST'])
|
||||
def get_peer_config():
|
||||
try:
|
||||
@@ -849,10 +887,14 @@ def get_peer_config():
|
||||
# Get server configuration
|
||||
server_config = wireguard_manager.get_server_config()
|
||||
|
||||
# Check if IP already has a subnet mask, if not add /32
|
||||
peer_ip = peer.get('ip', '10.0.0.2')
|
||||
peer_address = peer_ip if '/' in peer_ip else f"{peer_ip}/32"
|
||||
|
||||
# Generate client configuration using peer registry data
|
||||
config = f"""[Interface]
|
||||
PrivateKey = {peer.get('private_key', 'YOUR_PRIVATE_KEY_HERE')}
|
||||
Address = {peer.get('ip', '10.0.0.2')}/32
|
||||
Address = {peer_address}
|
||||
DNS = 8.8.8.8, 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
|
||||
+238
-5
@@ -331,13 +331,62 @@ AllowedIPs = {allowed_ips}
|
||||
raise
|
||||
|
||||
def _reload_wireguard_config(self):
|
||||
"""Reload WireGuard configuration"""
|
||||
"""Reload WireGuard configuration by updating the main config file"""
|
||||
try:
|
||||
# This would typically involve restarting the WireGuard service
|
||||
# or reloading the configuration
|
||||
logger.info("WireGuard configuration reloaded")
|
||||
# Read the main server configuration
|
||||
server_config_path = os.path.join(self.wg_config_dir, 'wg_confs', 'wg0.conf')
|
||||
if not os.path.exists(server_config_path):
|
||||
logger.error("Server configuration file not found")
|
||||
return False
|
||||
|
||||
with open(server_config_path, 'r') as f:
|
||||
server_content = f.read()
|
||||
|
||||
# Find the end of the [Interface] section
|
||||
lines = server_content.split('\n')
|
||||
interface_end = 0
|
||||
for i, line in enumerate(lines):
|
||||
if line.strip().startswith('[Peer]'):
|
||||
interface_end = i
|
||||
break
|
||||
else:
|
||||
interface_end = len(lines)
|
||||
|
||||
# Keep only the [Interface] section
|
||||
interface_lines = lines[:interface_end]
|
||||
|
||||
# Add all peer configurations
|
||||
peer_lines = []
|
||||
for filename in os.listdir(self.peers_dir):
|
||||
if filename.endswith('.conf') and not filename.endswith('_keys.json'):
|
||||
peer_file = os.path.join(self.peers_dir, filename)
|
||||
with open(peer_file, 'r') as f:
|
||||
peer_content = f.read().strip()
|
||||
if peer_content:
|
||||
peer_lines.append('') # Empty line before peer
|
||||
peer_lines.extend(peer_content.split('\n'))
|
||||
|
||||
# Combine interface and peer configurations
|
||||
new_content = '\n'.join(interface_lines + peer_lines)
|
||||
|
||||
# Write the updated configuration
|
||||
with open(server_config_path, 'w') as f:
|
||||
f.write(new_content)
|
||||
|
||||
# Restart WireGuard container to apply changes
|
||||
import subprocess
|
||||
result = subprocess.run(['docker', 'restart', 'cell-wireguard'],
|
||||
capture_output=True, text=True, timeout=30)
|
||||
if result.returncode == 0:
|
||||
logger.info("WireGuard configuration reloaded and container restarted")
|
||||
return True
|
||||
else:
|
||||
logger.error(f"Failed to restart WireGuard container: {result.stderr}")
|
||||
return False
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to reload WireGuard configuration: {e}")
|
||||
return False
|
||||
|
||||
def get_metrics(self) -> Dict[str, Any]:
|
||||
"""Get WireGuard metrics"""
|
||||
@@ -528,9 +577,13 @@ AllowedIPs = {allowed_ips}
|
||||
# Get peer private key from peer data
|
||||
peer_private_key = peer_info.get('private_key', 'YOUR_PRIVATE_KEY_HERE')
|
||||
|
||||
# Check if IP already has a subnet mask, if not add /32
|
||||
peer_ip = peer_info.get('ip', '10.0.0.2')
|
||||
peer_address = peer_ip if '/' in peer_ip else f"{peer_ip}/32"
|
||||
|
||||
config = f"""[Interface]
|
||||
PrivateKey = {peer_private_key}
|
||||
Address = {peer_info.get('ip', '10.0.0.2')}/32
|
||||
Address = {peer_address}
|
||||
DNS = 8.8.8.8, 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
@@ -661,3 +714,183 @@ PersistentKeepalive = {peer_info.get('persistent_keepalive', 25)}"""
|
||||
'public_key': 'SERVER_PUBLIC_KEY_PLACEHOLDER',
|
||||
'endpoint': 'YOUR_SERVER_IP:51820'
|
||||
}
|
||||
|
||||
def get_peer_status(self, public_key: str) -> Dict[str, Any]:
|
||||
"""Get status for a specific peer"""
|
||||
try:
|
||||
# Get WireGuard interface status
|
||||
result = subprocess.run(['wg', 'show'], capture_output=True, text=True, check=True)
|
||||
wg_output = result.stdout
|
||||
|
||||
# Parse the output to find the specific peer
|
||||
lines = wg_output.strip().split('\n')
|
||||
peer_info = {}
|
||||
in_peer = False
|
||||
|
||||
for line in lines:
|
||||
if line.startswith('peer:') and public_key in line:
|
||||
in_peer = True
|
||||
peer_info['public_key'] = public_key
|
||||
elif line.startswith('peer:') and public_key not in line:
|
||||
in_peer = False
|
||||
elif in_peer and line.startswith(' allowed ips:'):
|
||||
peer_info['allowed_ips'] = line.split(':', 1)[1].strip()
|
||||
elif in_peer and line.startswith(' latest handshake:'):
|
||||
handshake_str = line.split(':', 1)[1].strip()
|
||||
if handshake_str and handshake_str != '(none)':
|
||||
peer_info['latest_handshake'] = handshake_str
|
||||
peer_info['online'] = True
|
||||
else:
|
||||
peer_info['online'] = False
|
||||
elif in_peer and line.startswith(' transfer:'):
|
||||
transfer_str = line.split(':', 1)[1].strip()
|
||||
if transfer_str and transfer_str != '(none)':
|
||||
# Parse transfer data (e.g., "1.2 KiB received, 3.4 KiB sent")
|
||||
parts = transfer_str.split(',')
|
||||
if len(parts) >= 2:
|
||||
rx_part = parts[0].strip()
|
||||
tx_part = parts[1].strip()
|
||||
|
||||
# Extract numbers from strings like "1.2 KiB received"
|
||||
import re
|
||||
rx_match = re.search(r'([\d.]+)\s+(\w+)', rx_part)
|
||||
tx_match = re.search(r'([\d.]+)\s+(\w+)', tx_part)
|
||||
|
||||
if rx_match and tx_match:
|
||||
rx_value = float(rx_match.group(1))
|
||||
rx_unit = rx_match.group(2)
|
||||
tx_value = float(tx_match.group(1))
|
||||
tx_unit = tx_match.group(2)
|
||||
|
||||
# Convert to bytes
|
||||
def convert_to_bytes(value, unit):
|
||||
multipliers = {'B': 1, 'KiB': 1024, 'MiB': 1024**2, 'GiB': 1024**3}
|
||||
return int(value * multipliers.get(unit, 1))
|
||||
|
||||
peer_info['transfer_rx'] = convert_to_bytes(rx_value, rx_unit)
|
||||
peer_info['transfer_tx'] = convert_to_bytes(tx_value, tx_unit)
|
||||
|
||||
# Set default values if not found
|
||||
if 'online' not in peer_info:
|
||||
peer_info['online'] = False
|
||||
if 'transfer_rx' not in peer_info:
|
||||
peer_info['transfer_rx'] = 0
|
||||
if 'transfer_tx' not in peer_info:
|
||||
peer_info['transfer_tx'] = 0
|
||||
if 'latest_handshake' not in peer_info:
|
||||
peer_info['latest_handshake'] = None
|
||||
|
||||
return peer_info
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get peer status for {public_key}: {e}")
|
||||
return {'online': False, 'transfer_rx': 0, 'transfer_tx': 0, 'latest_handshake': None}
|
||||
|
||||
def setup_network_configuration(self) -> bool:
|
||||
"""Setup network configuration for internet access"""
|
||||
try:
|
||||
logger.info("Setting up network configuration for internet access...")
|
||||
|
||||
# Enable IP forwarding
|
||||
self._enable_ip_forwarding()
|
||||
|
||||
# Configure NAT and routing
|
||||
self._configure_nat_routing()
|
||||
|
||||
logger.info("Network configuration completed successfully")
|
||||
return True
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to setup network configuration: {e}")
|
||||
return False
|
||||
|
||||
def _enable_ip_forwarding(self):
|
||||
"""Enable IP forwarding"""
|
||||
try:
|
||||
# Enable IP forwarding in the container
|
||||
subprocess.run(['sh', '-c', 'echo 1 > /proc/sys/net/ipv4/ip_forward'], check=True)
|
||||
logger.info("IP forwarding enabled")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to enable IP forwarding: {e}")
|
||||
raise
|
||||
|
||||
def _configure_nat_routing(self):
|
||||
"""Configure NAT and routing for internet access"""
|
||||
try:
|
||||
# Get the main network interface
|
||||
result = subprocess.run(['ip', 'route', 'show', 'default'], capture_output=True, text=True, check=True)
|
||||
main_interface = result.stdout.split()[4] # Extract interface name
|
||||
|
||||
# Configure iptables rules
|
||||
rules = [
|
||||
# Allow forwarding for WireGuard interface
|
||||
f"iptables -A FORWARD -i wg0 -j ACCEPT",
|
||||
f"iptables -A FORWARD -o wg0 -j ACCEPT",
|
||||
|
||||
# NAT rule for internet access
|
||||
f"iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o {main_interface} -j MASQUERADE",
|
||||
|
||||
# Allow established and related connections
|
||||
"iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT"
|
||||
]
|
||||
|
||||
for rule in rules:
|
||||
try:
|
||||
subprocess.run(['sh', '-c', rule], check=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
logger.warning(f"Rule may already exist: {rule} - {e}")
|
||||
|
||||
logger.info(f"NAT and routing configured for interface {main_interface}")
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to configure NAT routing: {e}")
|
||||
raise
|
||||
|
||||
def get_network_status(self) -> Dict[str, Any]:
|
||||
"""Get network configuration status"""
|
||||
try:
|
||||
status = {
|
||||
'ip_forwarding': self._check_ip_forwarding(),
|
||||
'nat_rules': self._check_nat_rules(),
|
||||
'forwarding_rules': self._check_forwarding_rules(),
|
||||
'interface_status': self._check_interface_status(),
|
||||
'timestamp': datetime.utcnow().isoformat()
|
||||
}
|
||||
return status
|
||||
except Exception as e:
|
||||
logger.error(f"Failed to get network status: {e}")
|
||||
return {'error': str(e)}
|
||||
|
||||
def _check_ip_forwarding(self) -> bool:
|
||||
"""Check if IP forwarding is enabled"""
|
||||
try:
|
||||
# Check in WireGuard container
|
||||
result = subprocess.run(['docker', 'exec', 'cell-wireguard', 'cat', '/proc/sys/net/ipv4/ip_forward'], capture_output=True, text=True, check=True)
|
||||
return result.stdout.strip() == '1'
|
||||
except:
|
||||
return False
|
||||
|
||||
def _check_nat_rules(self) -> bool:
|
||||
"""Check if NAT rules are configured"""
|
||||
try:
|
||||
# Check in WireGuard container
|
||||
result = subprocess.run(['docker', 'exec', 'cell-wireguard', 'iptables', '-t', 'nat', '-L', 'POSTROUTING', '-n'], capture_output=True, text=True, check=True)
|
||||
return 'MASQUERADE' in result.stdout
|
||||
except:
|
||||
return False
|
||||
|
||||
def _check_forwarding_rules(self) -> bool:
|
||||
"""Check if forwarding rules are configured"""
|
||||
try:
|
||||
# Check in WireGuard container
|
||||
result = subprocess.run(['docker', 'exec', 'cell-wireguard', 'iptables', '-L', 'FORWARD', '-n'], capture_output=True, text=True, check=True)
|
||||
# Check for ACCEPT rules (which indicate forwarding is allowed)
|
||||
return 'ACCEPT' in result.stdout and len(result.stdout.strip().split('\n')) > 2
|
||||
except:
|
||||
return False
|
||||
|
||||
def _check_interface_status(self) -> bool:
|
||||
"""Check if WireGuard interface is up"""
|
||||
try:
|
||||
# Check in WireGuard container
|
||||
result = subprocess.run(['docker', 'exec', 'cell-wireguard', 'ip', 'link', 'show', 'wg0'], capture_output=True, text=True, check=True)
|
||||
return 'UP' in result.stdout
|
||||
except:
|
||||
return False
|
||||
+1
-1
@@ -57,7 +57,7 @@ services:
|
||||
restart: unless-stopped
|
||||
networks:
|
||||
- cell-network
|
||||
command: ["/bin/sh", "-c", "apk add --no-cache chrony && chronyd -d -f /etc/chrony/chrony.conf"]
|
||||
command: ["/bin/sh", "-c", "apk add --no-cache chrony && exec chronyd -d -f /etc/chrony/chrony.conf -n"]
|
||||
|
||||
# Email Server - Postfix + Dovecot
|
||||
mail:
|
||||
|
||||
@@ -0,0 +1,389 @@
|
||||
# Personal Internet Cell - Network Configuration Guide
|
||||
|
||||
This guide explains how to configure networking for the Personal Internet Cell to provide internet access to WireGuard VPN clients.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
1. [Overview](#overview)
|
||||
2. [Network Architecture](#network-architecture)
|
||||
3. [Quick Setup](#quick-setup)
|
||||
4. [Detailed Configuration](#detailed-configuration)
|
||||
5. [Troubleshooting](#troubleshooting)
|
||||
6. [Advanced Configuration](#advanced-configuration)
|
||||
7. [Security Considerations](#security-considerations)
|
||||
|
||||
## Overview
|
||||
|
||||
The Personal Internet Cell provides a complete VPN solution with internet access. This requires proper configuration of:
|
||||
|
||||
- **IP Forwarding**: Allow traffic to pass through the server
|
||||
- **NAT (Network Address Translation)**: Translate private IPs to public IPs
|
||||
- **Routing**: Direct traffic from VPN clients to the internet
|
||||
- **Firewall Rules**: Control traffic flow and security
|
||||
|
||||
## Network Architecture
|
||||
|
||||
```
|
||||
Internet
|
||||
│
|
||||
▼
|
||||
[Host Server] (195.178.106.244)
|
||||
│
|
||||
├── [Docker Network] (172.20.0.0/16)
|
||||
│ └── [WireGuard Container] (cell-wireguard)
|
||||
│ └── [WireGuard Interface] (wg0: 10.0.0.1/24)
|
||||
│
|
||||
└── [VPN Clients] (10.0.0.2-10.0.0.254/24)
|
||||
└── [Internet Access via NAT]
|
||||
```
|
||||
|
||||
### Key Components
|
||||
|
||||
- **Host Interface**: `eth0` (or main network interface)
|
||||
- **WireGuard Interface**: `wg0` (10.0.0.1/24)
|
||||
- **Client Network**: `10.0.0.0/24`
|
||||
- **NAT Translation**: Client IPs → Host IP
|
||||
|
||||
## Quick Setup
|
||||
|
||||
### 1. Run the Network Configuration Script
|
||||
|
||||
```bash
|
||||
# Make the script executable (if not already done)
|
||||
chmod +x /opt/pic/scripts/setup-network.sh
|
||||
|
||||
# Run the configuration
|
||||
sudo /opt/pic/scripts/setup-network.sh setup
|
||||
```
|
||||
|
||||
### 2. Verify Configuration
|
||||
|
||||
```bash
|
||||
# Check status
|
||||
sudo /opt/pic/scripts/setup-network.sh status
|
||||
|
||||
# Test configuration
|
||||
sudo /opt/pic/scripts/setup-network.sh test
|
||||
```
|
||||
|
||||
### 3. Connect a VPN Client
|
||||
|
||||
Use the generated WireGuard configuration to connect a client. The client should now have internet access.
|
||||
|
||||
## Detailed Configuration
|
||||
|
||||
### IP Forwarding
|
||||
|
||||
IP forwarding allows the server to route packets between different network interfaces.
|
||||
|
||||
**Enable on Host:**
|
||||
```bash
|
||||
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
|
||||
sysctl -p
|
||||
```
|
||||
|
||||
**Enable in Container:**
|
||||
```bash
|
||||
docker exec cell-wireguard sh -c "echo 1 > /proc/sys/net/ipv4/ip_forward"
|
||||
```
|
||||
|
||||
### NAT Configuration
|
||||
|
||||
NAT (Network Address Translation) allows VPN clients to access the internet using the server's public IP.
|
||||
|
||||
**Container NAT Rules:**
|
||||
```bash
|
||||
# Allow forwarding for WireGuard traffic
|
||||
iptables -A FORWARD -i wg0 -j ACCEPT
|
||||
iptables -A FORWARD -o wg0 -j ACCEPT
|
||||
|
||||
# NAT rule for internet access
|
||||
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
|
||||
```
|
||||
|
||||
**Host NAT Rules:**
|
||||
```bash
|
||||
# Allow traffic from WireGuard network
|
||||
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -o eth0 -j MASQUERADE
|
||||
iptables -A FORWARD -i wg0 -j ACCEPT
|
||||
iptables -A FORWARD -o wg0 -j ACCEPT
|
||||
```
|
||||
|
||||
### Routing Configuration
|
||||
|
||||
**WireGuard Interface Setup:**
|
||||
```bash
|
||||
# Create WireGuard interface
|
||||
ip link add dev wg0 type wireguard
|
||||
|
||||
# Set private key
|
||||
wg set wg0 private-key /path/to/private-key
|
||||
|
||||
# Set listen port
|
||||
wg set wg0 listen-port 51820
|
||||
|
||||
# Add IP address
|
||||
ip addr add 10.0.0.1/24 dev wg0
|
||||
|
||||
# Bring interface up
|
||||
ip link set wg0 up
|
||||
|
||||
# Add peers
|
||||
wg set wg0 peer <public-key> allowed-ips 10.0.0.2/32
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Common Issues
|
||||
|
||||
#### 1. VPN Connected but No Internet
|
||||
|
||||
**Symptoms:**
|
||||
- WireGuard shows connected
|
||||
- Can ping server (10.0.0.1)
|
||||
- Cannot access internet
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Check IP forwarding
|
||||
cat /proc/sys/net/ipv4/ip_forward
|
||||
# Should return 1
|
||||
|
||||
# Check NAT rules
|
||||
iptables -t nat -L POSTROUTING -n
|
||||
# Should show MASQUERADE rule for 10.0.0.0/24
|
||||
|
||||
# Check forwarding rules
|
||||
iptables -L FORWARD -n
|
||||
# Should show ACCEPT rules for wg0
|
||||
|
||||
# Restart network configuration
|
||||
sudo /opt/pic/scripts/setup-network.sh reset
|
||||
sudo /opt/pic/scripts/setup-network.sh setup
|
||||
```
|
||||
|
||||
#### 2. Cannot Connect to VPN
|
||||
|
||||
**Symptoms:**
|
||||
- WireGuard client cannot connect
|
||||
- No handshake in server logs
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Check WireGuard interface
|
||||
docker exec cell-wireguard wg show
|
||||
|
||||
# Check if port 51820 is open
|
||||
netstat -ulnp | grep 51820
|
||||
|
||||
# Check firewall rules
|
||||
ufw status
|
||||
iptables -L INPUT -n
|
||||
|
||||
# Check Docker port mapping
|
||||
docker port cell-wireguard
|
||||
```
|
||||
|
||||
#### 3. DNS Issues
|
||||
|
||||
**Symptoms:**
|
||||
- Can ping IP addresses
|
||||
- Cannot resolve domain names
|
||||
|
||||
**Solutions:**
|
||||
```bash
|
||||
# Check DNS configuration in client config
|
||||
# Should include: DNS = 8.8.8.8, 1.1.1.1
|
||||
|
||||
# Test DNS from container
|
||||
docker exec cell-wireguard nslookup google.com
|
||||
|
||||
# Check if DNS is being blocked
|
||||
docker exec cell-wireguard iptables -L -n | grep 53
|
||||
```
|
||||
|
||||
### Diagnostic Commands
|
||||
|
||||
```bash
|
||||
# Check network status
|
||||
sudo /opt/pic/scripts/setup-network.sh status
|
||||
|
||||
# Test connectivity from container
|
||||
docker exec cell-wireguard ping -c 3 8.8.8.8
|
||||
|
||||
# Check routing table
|
||||
docker exec cell-wireguard ip route show
|
||||
|
||||
# Check interface status
|
||||
docker exec cell-wireguard ip addr show wg0
|
||||
|
||||
# Check NAT rules
|
||||
docker exec cell-wireguard iptables -t nat -L -n
|
||||
|
||||
# Check forwarding rules
|
||||
docker exec cell-wireguard iptables -L FORWARD -n
|
||||
```
|
||||
|
||||
## Advanced Configuration
|
||||
|
||||
### Custom DNS Servers
|
||||
|
||||
To use custom DNS servers, modify the WireGuard client configuration:
|
||||
|
||||
```ini
|
||||
[Interface]
|
||||
PrivateKey = <private-key>
|
||||
Address = 10.0.0.2/32
|
||||
DNS = 1.1.1.1, 1.0.0.1, 8.8.8.8, 8.8.4.4
|
||||
|
||||
[Peer]
|
||||
PublicKey = <server-public-key>
|
||||
Endpoint = 195.178.106.244:51820
|
||||
AllowedIPs = 0.0.0.0/0
|
||||
PersistentKeepalive = 25
|
||||
```
|
||||
|
||||
### Split Tunneling
|
||||
|
||||
To allow only specific traffic through the VPN:
|
||||
|
||||
```ini
|
||||
[Peer]
|
||||
AllowedIPs = 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16
|
||||
# Only route private networks through VPN
|
||||
```
|
||||
|
||||
### Port Forwarding
|
||||
|
||||
To forward specific ports to VPN clients:
|
||||
|
||||
```bash
|
||||
# Forward port 8080 to client 10.0.0.2
|
||||
iptables -t nat -A PREROUTING -p tcp --dport 8080 -j DNAT --to-destination 10.0.0.2:8080
|
||||
iptables -A FORWARD -p tcp -d 10.0.0.2 --dport 8080 -j ACCEPT
|
||||
```
|
||||
|
||||
### Bandwidth Limiting
|
||||
|
||||
To limit bandwidth for VPN clients:
|
||||
|
||||
```bash
|
||||
# Install tc (traffic control)
|
||||
apt-get install iproute2
|
||||
|
||||
# Limit client 10.0.0.2 to 1Mbps
|
||||
tc qdisc add dev wg0 root handle 1: htb default 30
|
||||
tc class add dev wg0 parent 1: classid 1:1 htb rate 1mbit
|
||||
tc class add dev wg0 parent 1:1 classid 1:10 htb rate 1mbit ceil 1mbit
|
||||
tc filter add dev wg0 protocol ip parent 1:0 prio 1 u32 match ip dst 10.0.0.2 flowid 1:10
|
||||
```
|
||||
|
||||
## Security Considerations
|
||||
|
||||
### Firewall Rules
|
||||
|
||||
**Basic Security Rules:**
|
||||
```bash
|
||||
# Drop invalid packets
|
||||
iptables -A INPUT -m conntrack --ctstate INVALID -j DROP
|
||||
|
||||
# Allow established connections
|
||||
iptables -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# Allow WireGuard traffic
|
||||
iptables -A INPUT -p udp --dport 51820 -j ACCEPT
|
||||
|
||||
# Allow SSH (if needed)
|
||||
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
|
||||
|
||||
# Drop everything else
|
||||
iptables -A INPUT -j DROP
|
||||
```
|
||||
|
||||
### Client Isolation
|
||||
|
||||
To prevent clients from communicating with each other:
|
||||
|
||||
```bash
|
||||
# Block inter-client communication
|
||||
iptables -A FORWARD -i wg0 -o wg0 -j DROP
|
||||
```
|
||||
|
||||
### Logging
|
||||
|
||||
To log VPN traffic:
|
||||
|
||||
```bash
|
||||
# Log all WireGuard traffic
|
||||
iptables -A FORWARD -i wg0 -j LOG --log-prefix "WG-FORWARD: "
|
||||
iptables -A FORWARD -o wg0 -j LOG --log-prefix "WG-FORWARD: "
|
||||
|
||||
# Log NAT traffic
|
||||
iptables -t nat -A POSTROUTING -s 10.0.0.0/24 -j LOG --log-prefix "WG-NAT: "
|
||||
```
|
||||
|
||||
## Monitoring
|
||||
|
||||
### Real-time Monitoring
|
||||
|
||||
```bash
|
||||
# Monitor WireGuard connections
|
||||
watch -n 1 "docker exec cell-wireguard wg show"
|
||||
|
||||
# Monitor traffic
|
||||
watch -n 1 "docker exec cell-wireguard wg show wg0 transfer"
|
||||
|
||||
# Monitor NAT rules
|
||||
watch -n 1 "iptables -t nat -L POSTROUTING -n -v"
|
||||
```
|
||||
|
||||
### Log Analysis
|
||||
|
||||
```bash
|
||||
# Check system logs
|
||||
journalctl -u pic-network.service -f
|
||||
|
||||
# Check iptables logs
|
||||
tail -f /var/log/kern.log | grep WG-
|
||||
|
||||
# Check Docker logs
|
||||
docker logs cell-wireguard -f
|
||||
```
|
||||
|
||||
## Backup and Recovery
|
||||
|
||||
### Backup Configuration
|
||||
|
||||
```bash
|
||||
# Backup iptables rules
|
||||
iptables-save > /opt/pic/backups/iptables-backup-$(date +%Y%m%d).rules
|
||||
|
||||
# Backup WireGuard configuration
|
||||
cp /opt/pic/config/wireguard/wg_confs/wg0.conf /opt/pic/backups/wg0-backup-$(date +%Y%m%d).conf
|
||||
|
||||
# Backup network script
|
||||
cp /opt/pic/scripts/setup-network.sh /opt/pic/backups/setup-network-backup-$(date +%Y%m%d).sh
|
||||
```
|
||||
|
||||
### Restore Configuration
|
||||
|
||||
```bash
|
||||
# Restore iptables rules
|
||||
iptables-restore < /opt/pic/backups/iptables-backup-YYYYMMDD.rules
|
||||
|
||||
# Restore WireGuard configuration
|
||||
cp /opt/pic/backups/wg0-backup-YYYYMMDD.conf /opt/pic/config/wireguard/wg_confs/wg0.conf
|
||||
docker restart cell-wireguard
|
||||
```
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Check the troubleshooting section above
|
||||
2. Run the diagnostic commands
|
||||
3. Check the logs for error messages
|
||||
4. Verify your network configuration
|
||||
5. Test with a simple client configuration
|
||||
|
||||
For additional help, check the main Personal Internet Cell documentation or create an issue in the project repository.
|
||||
Executable
+268
@@ -0,0 +1,268 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Personal Internet Cell - Network Configuration Script
|
||||
# This script sets up proper routing and NAT for WireGuard VPN internet access
|
||||
|
||||
set -e
|
||||
|
||||
echo "🔧 Setting up Personal Internet Cell Network Configuration..."
|
||||
|
||||
# Configuration variables
|
||||
WG_INTERFACE="wg0"
|
||||
WG_NETWORK="10.0.0.0/24"
|
||||
WG_CONTAINER="cell-wireguard"
|
||||
HOST_INTERFACE="eth0" # This will be auto-detected
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
log_info() {
|
||||
echo -e "${BLUE}[INFO]${NC} $1"
|
||||
}
|
||||
|
||||
log_success() {
|
||||
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
||||
}
|
||||
|
||||
log_warning() {
|
||||
echo -e "${YELLOW}[WARNING]${NC} $1"
|
||||
}
|
||||
|
||||
log_error() {
|
||||
echo -e "${RED}[ERROR]${NC} $1"
|
||||
}
|
||||
|
||||
# Function to check if running as root
|
||||
check_root() {
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
log_error "This script must be run as root"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to detect the main network interface
|
||||
detect_interface() {
|
||||
# Try to detect the main interface (not loopback, not docker)
|
||||
HOST_INTERFACE=$(ip route | grep default | awk '{print $5}' | head -1)
|
||||
if [[ -z "$HOST_INTERFACE" ]]; then
|
||||
log_error "Could not detect main network interface"
|
||||
exit 1
|
||||
fi
|
||||
log_info "Detected main interface: $HOST_INTERFACE"
|
||||
}
|
||||
|
||||
# Function to enable IP forwarding
|
||||
enable_ip_forwarding() {
|
||||
log_info "Enabling IP forwarding..."
|
||||
echo 'net.ipv4.ip_forward = 1' >> /etc/sysctl.conf
|
||||
sysctl -p
|
||||
log_success "IP forwarding enabled"
|
||||
}
|
||||
|
||||
# Function to configure WireGuard container networking
|
||||
configure_wireguard_container() {
|
||||
log_info "Configuring WireGuard container networking..."
|
||||
|
||||
# Check if container is running
|
||||
if ! docker ps | grep -q "$WG_CONTAINER"; then
|
||||
log_error "WireGuard container is not running"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get container's main interface
|
||||
CONTAINER_INTERFACE=$(docker exec $WG_CONTAINER ip route | grep default | awk '{print $5}' | head -1)
|
||||
if [[ -z "$CONTAINER_INTERFACE" ]]; then
|
||||
CONTAINER_INTERFACE="eth0"
|
||||
fi
|
||||
|
||||
log_info "Container interface: $CONTAINER_INTERFACE"
|
||||
|
||||
# Configure iptables rules inside the container
|
||||
docker exec $WG_CONTAINER sh -c "
|
||||
# Enable IP forwarding
|
||||
echo 1 > /proc/sys/net/ipv4/ip_forward
|
||||
|
||||
# Add default route if missing
|
||||
ip route add default via 172.20.0.1 dev $CONTAINER_INTERFACE 2>/dev/null || true
|
||||
|
||||
# Clear existing rules (be careful!)
|
||||
iptables -t nat -F
|
||||
iptables -F FORWARD
|
||||
|
||||
# Allow forwarding for WireGuard interface
|
||||
iptables -A FORWARD -i $WG_INTERFACE -j ACCEPT
|
||||
iptables -A FORWARD -o $WG_INTERFACE -j ACCEPT
|
||||
|
||||
# NAT rule for internet access
|
||||
iptables -t nat -A POSTROUTING -s $WG_NETWORK -o $CONTAINER_INTERFACE -j MASQUERADE
|
||||
|
||||
# Allow established and related connections
|
||||
iptables -A FORWARD -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
|
||||
|
||||
# Log the configuration
|
||||
echo 'Network configuration applied:'
|
||||
echo 'IP Forwarding:'
|
||||
cat /proc/sys/net/ipv4/ip_forward
|
||||
echo 'Routing Table:'
|
||||
ip route show
|
||||
echo 'NAT Rules:'
|
||||
iptables -t nat -L POSTROUTING -n
|
||||
echo 'Forwarding Rules:'
|
||||
iptables -L FORWARD -n
|
||||
"
|
||||
|
||||
log_success "WireGuard container networking configured"
|
||||
}
|
||||
|
||||
# Function to configure host networking
|
||||
configure_host_networking() {
|
||||
log_info "Configuring host networking..."
|
||||
|
||||
# Enable IP forwarding on host
|
||||
echo 'net.ipv4.ip_forward = 1' > /etc/sysctl.d/99-wireguard.conf
|
||||
sysctl -p /etc/sysctl.d/99-wireguard.conf
|
||||
|
||||
# Configure iptables rules on host
|
||||
iptables -t nat -A POSTROUTING -s $WG_NETWORK -o $HOST_INTERFACE -j MASQUERADE
|
||||
iptables -A FORWARD -i $WG_INTERFACE -j ACCEPT
|
||||
iptables -A FORWARD -o $WG_INTERFACE -j ACCEPT
|
||||
|
||||
# Save iptables rules
|
||||
if command -v iptables-save >/dev/null 2>&1; then
|
||||
mkdir -p /etc/iptables
|
||||
iptables-save > /etc/iptables/rules.v4
|
||||
log_info "iptables rules saved"
|
||||
fi
|
||||
|
||||
log_success "Host networking configured"
|
||||
}
|
||||
|
||||
# Function to create persistent configuration
|
||||
create_persistent_config() {
|
||||
log_info "Creating persistent configuration..."
|
||||
|
||||
# Create systemd service for network configuration
|
||||
cat > /etc/systemd/system/pic-network.service << EOF
|
||||
[Unit]
|
||||
Description=Personal Internet Cell Network Configuration
|
||||
After=docker.service
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
RemainAfterExit=yes
|
||||
ExecStart=/opt/pic/scripts/setup-network.sh
|
||||
ExecReload=/opt/pic/scripts/setup-network.sh
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
# Enable the service
|
||||
systemctl daemon-reload
|
||||
systemctl enable pic-network.service
|
||||
|
||||
log_success "Persistent configuration created"
|
||||
}
|
||||
|
||||
# Function to test the configuration
|
||||
test_configuration() {
|
||||
log_info "Testing network configuration..."
|
||||
|
||||
# Check if WireGuard interface is up
|
||||
if docker exec $WG_CONTAINER ip link show $WG_INTERFACE >/dev/null 2>&1; then
|
||||
log_success "WireGuard interface is up"
|
||||
else
|
||||
log_error "WireGuard interface is not up"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check NAT rules
|
||||
if docker exec $WG_CONTAINER iptables -t nat -L POSTROUTING | grep -q MASQUERADE; then
|
||||
log_success "NAT rules are configured"
|
||||
else
|
||||
log_error "NAT rules are missing"
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Check IP forwarding
|
||||
if docker exec $WG_CONTAINER cat /proc/sys/net/ipv4/ip_forward | grep -q 1; then
|
||||
log_success "IP forwarding is enabled"
|
||||
else
|
||||
log_error "IP forwarding is not enabled"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "Network configuration test passed"
|
||||
}
|
||||
|
||||
# Function to show status
|
||||
show_status() {
|
||||
log_info "Network Configuration Status:"
|
||||
echo "=================================="
|
||||
|
||||
echo "WireGuard Interface:"
|
||||
docker exec $WG_CONTAINER ip addr show $WG_INTERFACE 2>/dev/null || echo " Interface not found"
|
||||
|
||||
echo -e "\nRouting Table:"
|
||||
docker exec $WG_CONTAINER ip route show
|
||||
|
||||
echo -e "\nNAT Rules:"
|
||||
docker exec $WG_CONTAINER iptables -t nat -L POSTROUTING -n
|
||||
|
||||
echo -e "\nForwarding Rules:"
|
||||
docker exec $WG_CONTAINER iptables -L FORWARD -n
|
||||
|
||||
echo -e "\nIP Forwarding Status:"
|
||||
echo " Container: $(docker exec $WG_CONTAINER cat /proc/sys/net/ipv4/ip_forward)"
|
||||
echo " Host: $(cat /proc/sys/net/ipv4/ip_forward)"
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log_info "Starting Personal Internet Cell Network Setup..."
|
||||
|
||||
check_root
|
||||
detect_interface
|
||||
|
||||
case "${1:-setup}" in
|
||||
"setup")
|
||||
enable_ip_forwarding
|
||||
configure_wireguard_container
|
||||
configure_host_networking
|
||||
create_persistent_config
|
||||
test_configuration
|
||||
log_success "Network configuration completed successfully!"
|
||||
;;
|
||||
"test")
|
||||
test_configuration
|
||||
;;
|
||||
"status")
|
||||
show_status
|
||||
;;
|
||||
"reset")
|
||||
log_warning "Resetting network configuration..."
|
||||
docker exec $WG_CONTAINER iptables -t nat -F
|
||||
docker exec $WG_CONTAINER iptables -F FORWARD
|
||||
iptables -t nat -D POSTROUTING -s $WG_NETWORK -o $HOST_INTERFACE -j MASQUERADE 2>/dev/null || true
|
||||
iptables -D FORWARD -i $WG_INTERFACE -j ACCEPT 2>/dev/null || true
|
||||
iptables -D FORWARD -o $WG_INTERFACE -j ACCEPT 2>/dev/null || true
|
||||
log_success "Network configuration reset"
|
||||
;;
|
||||
*)
|
||||
echo "Usage: $0 {setup|test|status|reset}"
|
||||
echo " setup - Configure network (default)"
|
||||
echo " test - Test configuration"
|
||||
echo " status - Show current status"
|
||||
echo " reset - Reset configuration"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
# Run main function
|
||||
main "$@"
|
||||
@@ -283,9 +283,12 @@ function Peers() {
|
||||
const serverAllowedIPs = peer.allowed_ips || "0.0.0.0/0";
|
||||
const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE';
|
||||
|
||||
// Check if IP already has a subnet mask, if not add /32
|
||||
const peerAddress = peer.ip.includes('/') ? peer.ip : `${peer.ip}/32`;
|
||||
|
||||
return `[Interface]
|
||||
PrivateKey = ${privateKey}
|
||||
Address = ${peer.ip}/32
|
||||
Address = ${peerAddress}
|
||||
DNS = 8.8.8.8, 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
@@ -301,11 +304,14 @@ PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
||||
const serverEndpoint = peer.server_endpoint || "YOUR_SERVER_IP:51820";
|
||||
const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE';
|
||||
|
||||
// Check if IP already has a subnet mask, if not add /32
|
||||
const peerAddress = peer.ip.includes('/') ? peer.ip : `${peer.ip}/32`;
|
||||
|
||||
// Create a config that's compatible with qrencode and mobile apps
|
||||
// Use proper spacing and format that WireGuard apps expect
|
||||
return `[Interface]
|
||||
PrivateKey = ${privateKey}
|
||||
Address = ${peer.ip}/32
|
||||
Address = ${peerAddress}
|
||||
DNS = 8.8.8.8, 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
|
||||
@@ -27,12 +27,18 @@ function Routing() {
|
||||
const [showFwForm, setShowFwForm] = useState(false);
|
||||
const [newFwRule, setNewFwRule] = useState({ rule_type: 'INPUT', source: '', destination: '', action: 'ACCEPT', protocol: 'ALL', port_range: '' });
|
||||
const [fwSubmitting, setFwSubmitting] = useState(false);
|
||||
// Network Configuration state
|
||||
const [networkStatus, setNetworkStatus] = useState(null);
|
||||
const [networkLoading, setNetworkLoading] = useState(false);
|
||||
const [networkError, setNetworkError] = useState(null);
|
||||
const [isSettingUp, setIsSettingUp] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
fetchRoutingStatus();
|
||||
fetchNatRules();
|
||||
fetchPeerRoutes();
|
||||
fetchFirewallRules();
|
||||
fetchNetworkStatus();
|
||||
}, []);
|
||||
|
||||
const fetchRoutingStatus = async () => {
|
||||
@@ -85,6 +91,53 @@ function Routing() {
|
||||
}
|
||||
};
|
||||
|
||||
const fetchNetworkStatus = async () => {
|
||||
setNetworkLoading(true);
|
||||
setNetworkError(null);
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/api/wireguard/network/status');
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
setNetworkStatus(data);
|
||||
} else {
|
||||
throw new Error('Failed to fetch network status');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch network status:', error);
|
||||
setNetworkError('Failed to fetch network status');
|
||||
} finally {
|
||||
setNetworkLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
const setupNetworkConfiguration = async () => {
|
||||
setIsSettingUp(true);
|
||||
setNetworkError(null);
|
||||
try {
|
||||
const response = await fetch('http://localhost:3000/api/wireguard/network/setup', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('Network setup successful:', data);
|
||||
// Refresh network status
|
||||
await fetchNetworkStatus();
|
||||
} else {
|
||||
const errorData = await response.json();
|
||||
throw new Error(errorData.error || 'Failed to setup network configuration');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to setup network configuration:', error);
|
||||
setNetworkError(error.message);
|
||||
} finally {
|
||||
setIsSettingUp(false);
|
||||
}
|
||||
};
|
||||
|
||||
const handleNatInputChange = (e) => {
|
||||
const { name, value, type, checked } = e.target;
|
||||
setNewNat((prev) => ({
|
||||
@@ -216,6 +269,7 @@ function Routing() {
|
||||
|
||||
const tabs = [
|
||||
{ id: 'overview', name: 'Overview', icon: Activity },
|
||||
{ id: 'network', name: 'Network Config', icon: Wifi },
|
||||
{ id: 'nat', name: 'NAT Rules', icon: Shield },
|
||||
{ id: 'peers', name: 'Peer Routes', icon: Wifi },
|
||||
{ id: 'firewall', name: 'Firewall', icon: Settings },
|
||||
@@ -332,6 +386,148 @@ function Routing() {
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'network' && (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
<h3 className="text-lg font-medium text-gray-900">Network Configuration</h3>
|
||||
<button
|
||||
className="btn btn-primary flex items-center"
|
||||
onClick={setupNetworkConfiguration}
|
||||
disabled={isSettingUp}
|
||||
>
|
||||
{isSettingUp ? (
|
||||
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
|
||||
) : (
|
||||
<Settings className="h-4 w-4 mr-2" />
|
||||
)}
|
||||
{isSettingUp ? 'Setting up...' : 'Setup Network'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{networkError && (
|
||||
<div className="mb-4 p-4 bg-red-50 border border-red-200 rounded-lg">
|
||||
<p className="text-red-800">{networkError}</p>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{networkLoading ? (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary-600"></div>
|
||||
</div>
|
||||
) : networkStatus ? (
|
||||
<div className="space-y-6">
|
||||
{/* Network Status Cards */}
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
<div className="bg-white p-4 rounded-lg border border-gray-200">
|
||||
<div className="flex items-center">
|
||||
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.ip_forwarding ? 'bg-green-400' : 'bg-red-400'}`}></div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">IP Forwarding</p>
|
||||
<p className="text-xs text-gray-500">{networkStatus.ip_forwarding ? 'Enabled' : 'Disabled'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg border border-gray-200">
|
||||
<div className="flex items-center">
|
||||
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.interface_status ? 'bg-green-400' : 'bg-red-400'}`}></div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">WireGuard Interface</p>
|
||||
<p className="text-xs text-gray-500">{networkStatus.interface_status ? 'Up' : 'Down'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg border border-gray-200">
|
||||
<div className="flex items-center">
|
||||
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.nat_rules ? 'bg-green-400' : 'bg-red-400'}`}></div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">NAT Rules</p>
|
||||
<p className="text-xs text-gray-500">{networkStatus.nat_rules ? 'Configured' : 'Missing'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="bg-white p-4 rounded-lg border border-gray-200">
|
||||
<div className="flex items-center">
|
||||
<div className={`w-3 h-3 rounded-full mr-3 ${networkStatus.forwarding_rules ? 'bg-green-400' : 'bg-red-400'}`}></div>
|
||||
<div>
|
||||
<p className="text-sm font-medium text-gray-900">Forwarding Rules</p>
|
||||
<p className="text-xs text-gray-500">{networkStatus.forwarding_rules ? 'Configured' : 'Missing'}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Configuration Details */}
|
||||
<div className="bg-gray-50 p-4 rounded-lg">
|
||||
<h4 className="text-md font-medium text-gray-900 mb-3">Configuration Details</h4>
|
||||
<div className="space-y-2 text-sm">
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Last Updated:</span>
|
||||
<span className="text-gray-900">{new Date(networkStatus.timestamp).toLocaleString()}</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">IP Forwarding:</span>
|
||||
<span className={`font-medium ${networkStatus.ip_forwarding ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{networkStatus.ip_forwarding ? 'Enabled' : 'Disabled'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">WireGuard Interface:</span>
|
||||
<span className={`font-medium ${networkStatus.interface_status ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{networkStatus.interface_status ? 'Up (wg0)' : 'Down'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">NAT Translation:</span>
|
||||
<span className={`font-medium ${networkStatus.nat_rules ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{networkStatus.nat_rules ? 'Active' : 'Not Configured'}
|
||||
</span>
|
||||
</div>
|
||||
<div className="flex justify-between">
|
||||
<span className="text-gray-600">Traffic Forwarding:</span>
|
||||
<span className={`font-medium ${networkStatus.forwarding_rules ? 'text-green-600' : 'text-red-600'}`}>
|
||||
{networkStatus.forwarding_rules ? 'Allowed' : 'Blocked'}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<div className="bg-blue-50 p-4 rounded-lg">
|
||||
<h4 className="text-md font-medium text-blue-900 mb-2">Quick Actions</h4>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
<button
|
||||
className="btn btn-sm btn-outline"
|
||||
onClick={fetchNetworkStatus}
|
||||
>
|
||||
Refresh Status
|
||||
</button>
|
||||
<button
|
||||
className="btn btn-sm btn-primary"
|
||||
onClick={setupNetworkConfiguration}
|
||||
disabled={isSettingUp}
|
||||
>
|
||||
{isSettingUp ? 'Setting up...' : 'Setup Network'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="text-center py-8">
|
||||
<p className="text-gray-500">Failed to load network status</p>
|
||||
<button
|
||||
className="btn btn-primary mt-2"
|
||||
onClick={fetchNetworkStatus}
|
||||
>
|
||||
Retry
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
{activeTab === 'nat' && (
|
||||
<div>
|
||||
<div className="flex justify-between items-center mb-4">
|
||||
|
||||
+323
-80
@@ -1,15 +1,19 @@
|
||||
import { useState, useEffect } from 'react';
|
||||
import { Shield, Key, Users, Activity, Wifi, Download, Copy, RefreshCw, Play, Pause, AlertCircle } from 'lucide-react';
|
||||
import { wireguardAPI } from '../services/api';
|
||||
import { Shield, Key, Users, Activity, Wifi, Download, Copy, RefreshCw, Play, Pause, AlertCircle, Eye } from 'lucide-react';
|
||||
import { wireguardAPI, peerAPI } from '../services/api';
|
||||
import QRCode from 'qrcode';
|
||||
|
||||
function WireGuard() {
|
||||
const [status, setStatus] = useState(null);
|
||||
const [peers, setPeers] = useState([]);
|
||||
const [totalPeers, setTotalPeers] = useState(0);
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [isRefreshing, setIsRefreshing] = useState(false);
|
||||
const [selectedPeer, setSelectedPeer] = useState(null);
|
||||
const [showPeerConfig, setShowPeerConfig] = useState(false);
|
||||
const [peerConfig, setPeerConfig] = useState('');
|
||||
const [qrCodeDataUrl, setQrCodeDataUrl] = useState('');
|
||||
const [peerStatuses, setPeerStatuses] = useState({});
|
||||
|
||||
useEffect(() => {
|
||||
fetchWireGuardData();
|
||||
@@ -17,13 +21,69 @@ function WireGuard() {
|
||||
|
||||
const fetchWireGuardData = async () => {
|
||||
try {
|
||||
const [statusResponse, peersResponse] = await Promise.all([
|
||||
const [statusResponse, peersResponse, wireguardResponse] = await Promise.all([
|
||||
wireguardAPI.getStatus(),
|
||||
peerAPI.getPeers(),
|
||||
wireguardAPI.getPeers()
|
||||
]);
|
||||
|
||||
setStatus(statusResponse.data);
|
||||
setPeers(peersResponse.data || []);
|
||||
|
||||
// Merge peer registry data with WireGuard data (same as Peers page)
|
||||
const peersData = peersResponse.data || [];
|
||||
const wireguardPeers = wireguardResponse.data || [];
|
||||
|
||||
// Create a map of WireGuard peers by name for quick lookup
|
||||
const wireguardMap = {};
|
||||
wireguardPeers.forEach(peer => {
|
||||
wireguardMap[peer.name] = peer;
|
||||
});
|
||||
|
||||
// Merge the data
|
||||
const mergedPeers = peersData.map(peer => ({
|
||||
...peer,
|
||||
...wireguardMap[peer.peer || peer.name],
|
||||
name: peer.peer || peer.name,
|
||||
status: 'Online', // For now, assume all peers are online
|
||||
type: 'WireGuard',
|
||||
// Preserve important fields that might be overwritten
|
||||
private_key: peer.private_key,
|
||||
server_public_key: peer.server_public_key,
|
||||
server_endpoint: peer.server_endpoint,
|
||||
allowed_ips: peer.allowed_ips || wireguardMap[peer.peer || peer.name]?.AllowedIPs || '0.0.0.0/0',
|
||||
persistent_keepalive: peer.persistent_keepalive || wireguardMap[peer.peer || peer.name]?.PersistentKeepalive || 25
|
||||
}));
|
||||
|
||||
// Load peer statuses first
|
||||
const statusPromises = mergedPeers.map(async (peer) => {
|
||||
if (peer.public_key) {
|
||||
const status = await getPeerStatus(peer);
|
||||
return { peerId: peer.name, status };
|
||||
}
|
||||
return { peerId: peer.name, status: { online: null, lastHandshake: null, transferRx: 0, transferTx: 0 } };
|
||||
});
|
||||
|
||||
const statusResults = await Promise.all(statusPromises);
|
||||
const statusMap = {};
|
||||
statusResults.forEach(({ peerId, status }) => {
|
||||
statusMap[peerId] = status;
|
||||
});
|
||||
setPeerStatuses(statusMap);
|
||||
|
||||
// Set total peers count
|
||||
setTotalPeers(mergedPeers.length);
|
||||
|
||||
// Filter to only show live connected peers
|
||||
const livePeers = mergedPeers.filter(peer => {
|
||||
const peerStatus = statusMap[peer.name];
|
||||
return peerStatus && (
|
||||
peerStatus.online === true ||
|
||||
(peerStatus.lastHandshake && peerStatus.lastHandshake !== null) ||
|
||||
(peerStatus.transferRx > 0 || peerStatus.transferTx > 0)
|
||||
);
|
||||
});
|
||||
|
||||
setPeers(livePeers);
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch WireGuard data:', error);
|
||||
} finally {
|
||||
@@ -40,11 +100,39 @@ function WireGuard() {
|
||||
const handleViewPeerConfig = async (peer) => {
|
||||
setSelectedPeer(peer);
|
||||
try {
|
||||
// Try to get existing config first
|
||||
const response = await wireguardAPI.getPeerConfig({ name: peer.name });
|
||||
setPeerConfig(response.data.config || 'Configuration not available');
|
||||
let config = response.data.config;
|
||||
|
||||
// If no config exists, generate a complete one with real server config
|
||||
if (!config || config === 'Configuration not available') {
|
||||
// Get server configuration first
|
||||
const serverConfig = await getServerConfig();
|
||||
|
||||
// Create peer with server config
|
||||
const peerWithServerConfig = {
|
||||
...peer,
|
||||
server_public_key: serverConfig.public_key,
|
||||
server_endpoint: serverConfig.endpoint
|
||||
};
|
||||
|
||||
config = generateWireGuardConfig(peerWithServerConfig);
|
||||
}
|
||||
|
||||
setPeerConfig(config);
|
||||
|
||||
// Generate QR code for the config
|
||||
try {
|
||||
const qrDataUrl = await generateQRCode(config);
|
||||
setQrCodeDataUrl(qrDataUrl);
|
||||
} catch (qrError) {
|
||||
console.error('Failed to generate QR code:', qrError);
|
||||
setQrCodeDataUrl('');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get peer config:', error);
|
||||
setPeerConfig('Failed to load configuration');
|
||||
setQrCodeDataUrl('');
|
||||
}
|
||||
setShowPeerConfig(true);
|
||||
};
|
||||
@@ -71,6 +159,68 @@ function WireGuard() {
|
||||
URL.revokeObjectURL(url);
|
||||
};
|
||||
|
||||
const getServerConfig = async () => {
|
||||
try {
|
||||
// Try to get server configuration from API
|
||||
const response = await fetch('/api/wireguard/server-config');
|
||||
if (response.ok) {
|
||||
const config = await response.json();
|
||||
return {
|
||||
public_key: config.public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER",
|
||||
endpoint: config.endpoint || "YOUR_SERVER_IP:51820"
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.warn('Could not get server config:', error);
|
||||
}
|
||||
|
||||
// Return default values
|
||||
return {
|
||||
public_key: "SERVER_PUBLIC_KEY_PLACEHOLDER",
|
||||
endpoint: "YOUR_SERVER_IP:51820"
|
||||
};
|
||||
};
|
||||
|
||||
const generateWireGuardConfig = (peer) => {
|
||||
// Use real keys from the peer data
|
||||
const serverPublicKey = peer.server_public_key || "SERVER_PUBLIC_KEY_PLACEHOLDER";
|
||||
const serverEndpoint = peer.server_endpoint || "YOUR_SERVER_IP:51820";
|
||||
const serverAllowedIPs = peer.allowed_ips || "0.0.0.0/0";
|
||||
const privateKey = peer.private_key || 'YOUR_PRIVATE_KEY_HERE';
|
||||
|
||||
// Check if IP already has a subnet mask, if not add /32
|
||||
const peerAddress = peer.ip.includes('/') ? peer.ip : `${peer.ip}/32`;
|
||||
|
||||
return `[Interface]
|
||||
PrivateKey = ${privateKey}
|
||||
Address = ${peerAddress}
|
||||
DNS = 8.8.8.8, 1.1.1.1
|
||||
|
||||
[Peer]
|
||||
PublicKey = ${serverPublicKey}
|
||||
Endpoint = ${serverEndpoint}
|
||||
AllowedIPs = ${serverAllowedIPs}
|
||||
PersistentKeepalive = ${peer.persistent_keepalive || 25}`;
|
||||
};
|
||||
|
||||
const generateQRCode = async (text) => {
|
||||
try {
|
||||
const qrDataUrl = await QRCode.toDataURL(text, {
|
||||
width: 256,
|
||||
margin: 2,
|
||||
color: {
|
||||
dark: '#000000',
|
||||
light: '#FFFFFF'
|
||||
},
|
||||
errorCorrectionLevel: 'M'
|
||||
});
|
||||
return qrDataUrl;
|
||||
} catch (error) {
|
||||
console.error('QR Code generation error:', error);
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
const formatBytes = (bytes) => {
|
||||
if (bytes === 0) return '0 B';
|
||||
const k = 1024;
|
||||
@@ -79,15 +229,45 @@ function WireGuard() {
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
};
|
||||
|
||||
const getPeerStatus = (peer) => {
|
||||
// This would need to be implemented based on actual peer status
|
||||
// For now, we'll show a mock status
|
||||
return {
|
||||
online: Math.random() > 0.3, // Random for demo
|
||||
lastHandshake: new Date(Date.now() - Math.random() * 3600000).toISOString(),
|
||||
transferRx: Math.floor(Math.random() * 1000000),
|
||||
transferTx: Math.floor(Math.random() * 1000000)
|
||||
};
|
||||
const getPeerStatus = async (peer) => {
|
||||
try {
|
||||
// Get real peer status from the API
|
||||
const response = await fetch('http://localhost:3000/api/wireguard/peers/status', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
public_key: peer.public_key
|
||||
})
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const status = await response.json();
|
||||
return {
|
||||
online: status.online,
|
||||
lastHandshake: status.latest_handshake,
|
||||
transferRx: status.transfer_rx || 0,
|
||||
transferTx: status.transfer_tx || 0
|
||||
};
|
||||
} else {
|
||||
console.error('Failed to get peer status:', response.status);
|
||||
return {
|
||||
online: null,
|
||||
lastHandshake: null,
|
||||
transferRx: 0,
|
||||
transferTx: 0
|
||||
};
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error getting peer status:', error);
|
||||
return {
|
||||
online: null,
|
||||
lastHandshake: null,
|
||||
transferRx: 0,
|
||||
transferTx: 0
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
@@ -142,7 +322,7 @@ function WireGuard() {
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p className="text-sm font-medium text-gray-500">Total Peers</p>
|
||||
<p className="text-lg font-semibold text-gray-900">{peers.length}</p>
|
||||
<p className="text-lg font-semibold text-gray-900">{totalPeers}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -153,9 +333,9 @@ function WireGuard() {
|
||||
<Activity className="h-6 w-6 text-green-600" />
|
||||
</div>
|
||||
<div className="ml-4">
|
||||
<p className="text-sm font-medium text-gray-500">Active Peers</p>
|
||||
<p className="text-sm font-medium text-gray-500">Live Connections</p>
|
||||
<p className="text-lg font-semibold text-gray-900">
|
||||
{peers.filter(peer => getPeerStatus(peer).online).length}
|
||||
{peers.length}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -199,28 +379,31 @@ function WireGuard() {
|
||||
)}
|
||||
|
||||
{/* Peers Table */}
|
||||
<div className="card">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center">
|
||||
<Users className="h-6 w-6 text-primary-500 mr-2" />
|
||||
<h3 className="text-lg font-medium text-gray-900">Connected Peers</h3>
|
||||
<div className="card">
|
||||
<div className="flex items-center justify-between mb-6">
|
||||
<div className="flex items-center">
|
||||
<Users className="h-6 w-6 text-primary-500 mr-2" />
|
||||
<h3 className="text-lg font-medium text-gray-900">Live Connected Peers</h3>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{peers.length} peer{peers.length !== 1 ? 's' : ''} currently connected
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm text-gray-500">
|
||||
{peers.length} peer{peers.length !== 1 ? 's' : ''} configured
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{peers.length === 0 ? (
|
||||
<div className="text-center py-12">
|
||||
<Shield className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">No peers configured</h3>
|
||||
<p className="text-gray-500 mb-4">Add your first peer to start using WireGuard VPN</p>
|
||||
<button
|
||||
onClick={() => window.location.href = '/peers'}
|
||||
className="btn btn-primary"
|
||||
>
|
||||
Add Peer
|
||||
</button>
|
||||
<Wifi className="h-12 w-12 text-gray-400 mx-auto mb-4" />
|
||||
<h3 className="text-lg font-medium text-gray-900 mb-2">No active connections</h3>
|
||||
<p className="text-gray-500 mb-4">No peers are currently connected to the WireGuard VPN</p>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-400">Configured peers will appear here when they connect</p>
|
||||
<button
|
||||
onClick={() => window.location.href = '/peers'}
|
||||
className="btn btn-secondary"
|
||||
>
|
||||
Manage Peers
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="overflow-x-auto">
|
||||
@@ -249,7 +432,7 @@ function WireGuard() {
|
||||
</thead>
|
||||
<tbody className="bg-white divide-y divide-gray-200">
|
||||
{peers.map((peer, index) => {
|
||||
const peerStatus = getPeerStatus(peer);
|
||||
const peerStatus = peerStatuses[peer.name] || { online: null, lastHandshake: null, transferRx: 0, transferTx: 0 };
|
||||
return (
|
||||
<tr key={index} className="hover:bg-gray-50">
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
@@ -272,31 +455,35 @@ function WireGuard() {
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap">
|
||||
<span className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
|
||||
peerStatus.online
|
||||
peerStatus.online === true
|
||||
? 'bg-green-100 text-green-800'
|
||||
: 'bg-red-100 text-red-800'
|
||||
: peerStatus.online === false
|
||||
? 'bg-red-100 text-red-800'
|
||||
: 'bg-gray-100 text-gray-800'
|
||||
}`}>
|
||||
<div className={`w-1.5 h-1.5 rounded-full mr-1.5 ${
|
||||
peerStatus.online ? 'bg-green-400' : 'bg-red-400'
|
||||
peerStatus.online === true ? 'bg-green-400' :
|
||||
peerStatus.online === false ? 'bg-red-400' : 'bg-gray-400'
|
||||
}`} />
|
||||
{peerStatus.online ? 'Online' : 'Offline'}
|
||||
{peerStatus.online === true ? 'Online' :
|
||||
peerStatus.online === false ? 'Offline' : 'Unknown'}
|
||||
</span>
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
{peerStatus.online
|
||||
{peerStatus.lastHandshake
|
||||
? new Date(peerStatus.lastHandshake).toLocaleString()
|
||||
: 'Never'
|
||||
: 'Unknown'
|
||||
}
|
||||
</td>
|
||||
<td className="px-6 py-4 whitespace-nowrap text-sm text-gray-500">
|
||||
<div className="space-y-1">
|
||||
<div className="flex items-center text-xs">
|
||||
<span className="text-green-600 mr-1">↓</span>
|
||||
{formatBytes(peerStatus.transferRx)}
|
||||
{peerStatus.transferRx > 0 ? formatBytes(peerStatus.transferRx) : 'No data'}
|
||||
</div>
|
||||
<div className="flex items-center text-xs">
|
||||
<span className="text-blue-600 mr-1">↑</span>
|
||||
{formatBytes(peerStatus.transferTx)}
|
||||
{peerStatus.transferTx > 0 ? formatBytes(peerStatus.transferTx) : 'No data'}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
@@ -307,14 +494,16 @@ function WireGuard() {
|
||||
className="text-primary-600 hover:text-primary-900"
|
||||
title="View Configuration"
|
||||
>
|
||||
<Copy className="h-4 w-4" />
|
||||
<Eye className="h-4 w-4" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => downloadConfig(peer.name, peerConfig)}
|
||||
className="text-primary-600 hover:text-primary-900"
|
||||
title="Download Config"
|
||||
onClick={() => handleViewPeerConfig(peer)}
|
||||
className="text-green-600 hover:text-green-900"
|
||||
title="Show QR Code"
|
||||
>
|
||||
<Download className="h-4 w-4" />
|
||||
<div className="h-4 w-4 border-2 border-current rounded-sm flex items-center justify-center">
|
||||
<div className="w-2 h-2 bg-current rounded-sm"></div>
|
||||
</div>
|
||||
</button>
|
||||
</div>
|
||||
</td>
|
||||
@@ -383,39 +572,93 @@ function WireGuard() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
WireGuard Configuration
|
||||
</label>
|
||||
<div className="relative">
|
||||
<textarea
|
||||
value={peerConfig}
|
||||
readOnly
|
||||
className="w-full h-64 p-3 border border-gray-300 rounded-md font-mono text-sm bg-gray-50"
|
||||
placeholder="Loading configuration..."
|
||||
/>
|
||||
<div className="absolute top-2 right-2 flex space-x-2">
|
||||
<button
|
||||
onClick={() => copyToClipboard(peerConfig)}
|
||||
className="btn btn-secondary btn-sm flex items-center"
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
<Copy className="h-4 w-4 mr-1" />
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
onClick={() => downloadConfig(selectedPeer.name, peerConfig)}
|
||||
className="btn btn-secondary btn-sm flex items-center"
|
||||
title="Download config file"
|
||||
>
|
||||
<Download className="h-4 w-4 mr-1" />
|
||||
Download
|
||||
</button>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2">
|
||||
WireGuard Configuration
|
||||
</label>
|
||||
<div className="relative">
|
||||
<textarea
|
||||
value={peerConfig}
|
||||
readOnly
|
||||
className="w-full h-64 p-3 border border-gray-300 rounded-md font-mono text-sm bg-gray-50"
|
||||
placeholder="Loading configuration..."
|
||||
/>
|
||||
<div className="absolute top-2 right-2 flex space-x-2">
|
||||
<button
|
||||
onClick={() => copyToClipboard(peerConfig)}
|
||||
className="btn btn-secondary btn-sm flex items-center"
|
||||
title="Copy to clipboard"
|
||||
>
|
||||
<Copy className="h-4 w-4 mr-1" />
|
||||
Copy
|
||||
</button>
|
||||
<button
|
||||
onClick={() => downloadConfig(selectedPeer.name, peerConfig)}
|
||||
className="btn btn-secondary btn-sm flex items-center"
|
||||
title="Download config file"
|
||||
>
|
||||
<Download className="h-4 w-4 mr-1" />
|
||||
Download
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
Use this configuration on your mobile device or computer to connect to this WireGuard network.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* QR Code Section */}
|
||||
<div>
|
||||
<label className="block text-sm font-medium text-gray-700 mb-2 flex items-center">
|
||||
<div className="h-5 w-5 mr-2 border-2 border-gray-400 rounded-sm flex items-center justify-center">
|
||||
<div className="w-2 h-2 bg-gray-400 rounded-sm"></div>
|
||||
</div>
|
||||
QR Code
|
||||
</label>
|
||||
<div className="text-center">
|
||||
{qrCodeDataUrl ? (
|
||||
<div className="space-y-4">
|
||||
<div className="inline-block p-4 bg-white border-2 border-gray-200 rounded-lg">
|
||||
<img
|
||||
src={qrCodeDataUrl}
|
||||
alt="WireGuard QR Code"
|
||||
className="w-48 h-48 mx-auto"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<p className="text-sm text-gray-600">
|
||||
Scan this QR code with your WireGuard app to connect automatically
|
||||
</p>
|
||||
<div className="flex justify-center space-x-2">
|
||||
<button
|
||||
onClick={() => {
|
||||
const link = document.createElement('a');
|
||||
link.href = qrCodeDataUrl;
|
||||
link.download = `${selectedPeer.name}-qrcode.png`;
|
||||
link.click();
|
||||
}}
|
||||
className="btn btn-sm btn-secondary flex items-center"
|
||||
title="Download QR Code"
|
||||
>
|
||||
<Download className="h-4 w-4 mr-1" />
|
||||
Download QR
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className="p-8 bg-gray-50 border-2 border-dashed border-gray-300 rounded-lg">
|
||||
<div className="h-12 w-12 border-2 border-gray-400 rounded-sm flex items-center justify-center mx-auto mb-2">
|
||||
<div className="w-6 h-6 bg-gray-400 rounded-sm"></div>
|
||||
</div>
|
||||
<p className="text-sm text-gray-500">
|
||||
QR Code will appear here
|
||||
</p>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
<p className="text-xs text-gray-500 mt-2">
|
||||
Use this configuration on your mobile device or computer to connect to this WireGuard network.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user