Appearance
Outline VPN Cheatsheet
Outline VPN is an open-source VPN solution developed by Jigsaw (a subsidiary of Alphabet Inc.) that prioritizes ease of use, security, and resistance to blocking. Built on the Shadowsocks protocol, Outline provides a simple way to create and manage personal VPN servers while offering strong encryption and obfuscation capabilities that make it difficult for network administrators to detect and block VPN traffic.
Platform Overview
Architecture and Design Philosophy
Outline VPN consists of two main components: the Outline Manager for server administration and the Outline Client for end-user connectivity. The system is designed with simplicity and security as core principles, making it accessible to users without extensive technical knowledge while maintaining enterprise-grade security standards.
The Outline Manager is a desktop application that allows administrators to deploy and manage Outline servers on various cloud platforms including DigitalOcean, Google Cloud Platform, Amazon Web Services, and Vultr. The manager handles server provisioning, user key generation, and traffic monitoring through an intuitive graphical interface.
The Outline Client applications are available for Windows, macOS, Linux, iOS, and Android platforms. These clients use the generated access keys to establish secure connections to Outline servers, providing encrypted tunneling for all network traffic.
Key Features
bash
# Core Outline VPN Capabilities
- Shadowsocks-based protocol for enhanced obfuscation
- Automatic server deployment on major cloud platforms
- Simple access key sharing system
- Cross-platform client applications
- Traffic monitoring and usage analytics
- Resistance to deep packet inspection (DPI)
- No logging policy and privacy-focused design
- Open-source transparency and auditability
Server Installation and Setup
Manual Server Installation
bash
# Install Outline server on Ubuntu/Debian
curl -sS https://raw.githubusercontent.com/Jigsaw-Code/outline-server/master/src/server_manager/install_scripts/install_server.sh | bash
# The installation script will:
# 1. Install Docker and Docker Compose
# 2. Download and run the Outline server container
# 3. Generate initial configuration
# 4. Display management API URL and certificate fingerprint
# Verify installation
sudo docker ps | grep outline
# Check server status
sudo docker logs outline-server
# View server configuration
sudo cat /opt/outline/access.txt
# Manual Docker installation (alternative method)
sudo docker run -d \
--name outline-server \
--restart unless-stopped \
-p 443:443/tcp \
-p 443:443/udp \
-p 8080:8080/tcp \
-v outline-data:/root/shadowbox/persisted-state \
quay.io/outline/shadowbox:stable
# Configure firewall for Outline server
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw allow 8080/tcp
sudo ufw enable
# Enable IP forwarding
echo 'net.ipv4.ip_forward=1' | sudo tee -a /etc/sysctl.conf
echo 'net.ipv6.conf.all.forwarding=1' | sudo tee -a /etc/sysctl.conf
sudo sysctl -p
Cloud Platform Deployment
bash
# DigitalOcean deployment script
#!/bin/bash
DROPLET_NAME="outline-vpn-server"
REGION="nyc3"
SIZE="s-1vcpu-1gb"
IMAGE="ubuntu-20-04-x64"
# Create droplet
doctl compute droplet create $DROPLET_NAME \
--region $REGION \
--size $SIZE \
--image $IMAGE \
--ssh-keys $(doctl compute ssh-key list --format ID --no-header | tr '\n' ',') \
--wait
# Get droplet IP
DROPLET_IP=$(doctl compute droplet list --format Name,PublicIPv4 --no-header | grep $DROPLET_NAME | awk '{print $2}')
# Install Outline server
ssh root@$DROPLET_IP 'curl -sS https://raw.githubusercontent.com/Jigsaw-Code/outline-server/master/src/server_manager/install_scripts/install_server.sh | bash'
# AWS EC2 deployment with Terraform
cat > outline-server.tf << 'EOF'
provider "aws" {
region = "us-east-1"
}
resource "aws_instance" "outline_server" {
ami = "ami-0c02fb55956c7d316" # Ubuntu 20.04 LTS
instance_type = "t3.micro"
key_name = "your-key-pair"
vpc_security_group_ids = [aws_security_group.outline_sg.id]
user_data = <<-EOF
#!/bin/bash
curl -sS https://raw.githubusercontent.com/Jigsaw-Code/outline-server/master/src/server_manager/install_scripts/install_server.sh | bash
EOF
tags = {
Name = "Outline VPN Server"
}
}
resource "aws_security_group" "outline_sg" {
name_prefix = "outline-server-"
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "udp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 8080
to_port = 8080
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
output "server_ip" {
value = aws_instance.outline_server.public_ip
}
EOF
# Deploy with Terraform
terraform init
terraform plan
terraform apply
Docker Compose Setup
yaml
# docker-compose.yml for Outline server
version: '3.8'
services:
outline-server:
image: quay.io/outline/shadowbox:stable
container_name: outline-server
restart: unless-stopped
ports:
- "443:443/tcp"
- "443:443/udp"
- "8080:8080/tcp"
volumes:
- outline-data:/root/shadowbox/persisted-state
- /etc/letsencrypt:/etc/letsencrypt:ro
environment:
- SB_API_PORT=8080
- SB_CERTIFICATE_FILE=/etc/letsencrypt/live/your-domain.com/fullchain.pem
- SB_PRIVATE_KEY_FILE=/etc/letsencrypt/live/your-domain.com/privkey.pem
networks:
- outline-network
watchtower:
image: containrrr/watchtower
container_name: outline-watchtower
restart: unless-stopped
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --interval 86400 outline-server
networks:
- outline-network
volumes:
outline-data:
networks:
outline-network:
driver: bridge
Outline Manager Configuration
Initial Setup and Server Connection
bash
# Download Outline Manager
# Windows: https://github.com/Jigsaw-Code/outline-client/releases/latest/download/Outline-Manager.exe
# macOS: https://github.com/Jigsaw-Code/outline-client/releases/latest/download/Outline-Manager.dmg
# Linux: https://github.com/Jigsaw-Code/outline-client/releases/latest/download/Outline-Manager.AppImage
# Connect to existing server using management API
# Format: https://server-ip:8080/management-api-url
# Example connection string from server installation:
# {"apiUrl":"https://192.168.1.100:8080/management-api-url","certSha256":"certificate-fingerprint"}
# Verify server connection
curl -k -H "Content-Type: application/json" \
"https://your-server-ip:8080/management-api-url/server"
# Get server information
curl -k -H "Content-Type: application/json" \
"https://your-server-ip:8080/management-api-url/server/info"
# Example response:
# {
# "name": "Outline Server",
# "serverId": "server-id-123",
# "metricsEnabled": true,
# "createdTimestampMs": 1640995200000,
# "version": "1.8.0",
# "accessKeyDataLimit": null,
# "portForNewAccessKeys": 443
# }
Access Key Management
bash
# Create new access key
curl -k -X POST \
-H "Content-Type: application/json" \
"https://your-server-ip:8080/management-api-url/access-keys"
# Example response:
# {
# "id": "0",
# "name": "User 1",
# "password": "password123",
# "port": 443,
# "method": "chacha20-ietf-poly1305",
# "accessUrl": "ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwYXNzd29yZDEyMw@192.168.1.100:443/?outline=1"
# }
# List all access keys
curl -k -H "Content-Type: application/json" \
"https://your-server-ip:8080/management-api-url/access-keys"
# Rename access key
curl -k -X PUT \
-H "Content-Type: application/json" \
-d '{"name": "John Doe"}' \
"https://your-server-ip:8080/management-api-url/access-keys/0/name"
# Set data limit for access key (in bytes)
curl -k -X PUT \
-H "Content-Type: application/json" \
-d '{"limit": {"bytes": 10737418240}}' \
"https://your-server-ip:8080/management-api-url/access-keys/0/data-limit"
# Remove data limit
curl -k -X DELETE \
"https://your-server-ip:8080/management-api-url/access-keys/0/data-limit"
# Delete access key
curl -k -X DELETE \
"https://your-server-ip:8080/management-api-url/access-keys/0"
# Get access key metrics
curl -k -H "Content-Type: application/json" \
"https://your-server-ip:8080/management-api-url/metrics/transfer"
# Example metrics response:
# {
# "bytesTransferredByUserId": {
# "0": 1073741824,
# "1": 2147483648
# }
# }
Server Configuration
bash
# Change server name
curl -k -X PUT \
-H "Content-Type: application/json" \
-d '{"name": "Company VPN Server"}' \
"https://your-server-ip:8080/management-api-url/server/name"
# Set default port for new access keys
curl -k -X PUT \
-H "Content-Type: application/json" \
-d '{"port": 8443}' \
"https://your-server-ip:8080/management-api-url/server/port-for-new-access-keys"
# Enable/disable metrics collection
curl -k -X PUT \
-H "Content-Type: application/json" \
-d '{"metricsEnabled": true}' \
"https://your-server-ip:8080/management-api-url/server/metrics-enabled"
# Set server-wide data limit
curl -k -X PUT \
-H "Content-Type: application/json" \
-d '{"limit": {"bytes": 107374182400}}' \
"https://your-server-ip:8080/management-api-url/server/access-key-data-limit"
# Get server configuration
curl -k -H "Content-Type: application/json" \
"https://your-server-ip:8080/management-api-url/server"
Client Configuration and Usage
Windows Client Setup
powershell
# Download and install Outline Client for Windows
$OutlineClientUrl = "https://github.com/Jigsaw-Code/outline-client/releases/latest/download/Outline-Client.exe"
$OutlineClientPath = "$env:TEMP\Outline-Client.exe"
Invoke-WebRequest -Uri $OutlineClientUrl -OutFile $OutlineClientPath
Start-Process -FilePath $OutlineClientPath -Wait
# Import access key via command line (if supported)
# Note: Outline Client primarily uses GUI for key import
# Access key format: ss://base64-encoded-config@server:port/?outline=1
# Example access key:
# ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwYXNzd29yZDEyMw@192.168.1.100:443/?outline=1
# Registry settings for Outline Client (advanced configuration)
$OutlineRegPath = "HKCU:\Software\Outline"
if (!(Test-Path $OutlineRegPath)) {
New-Item -Path $OutlineRegPath -Force
}
# Set auto-connect on startup
Set-ItemProperty -Path $OutlineRegPath -Name "AutoConnect" -Value 1
# Configure proxy settings
Set-ItemProperty -Path $OutlineRegPath -Name "ProxyMode" -Value "auto"
macOS Client Setup
bash
# Download Outline Client for macOS
curl -L -o "Outline-Client.dmg" \
"https://github.com/Jigsaw-Code/outline-client/releases/latest/download/Outline-Client.dmg"
# Mount and install
hdiutil mount Outline-Client.dmg
cp -R "/Volumes/Outline Client/Outline Client.app" /Applications/
hdiutil unmount "/Volumes/Outline Client"
# Launch Outline Client
open "/Applications/Outline Client.app"
# Import access key via URL scheme (if supported)
open "ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwYXNzd29yZDEyMw@192.168.1.100:443/?outline=1"
# Configure system proxy settings
networksetup -setautoproxyurl "Wi-Fi" "http://127.0.0.1:1080/proxy.pac"
# Check VPN connection status
scutil --nc list | grep -i outline
Linux Client Setup
bash
# Download Outline Client AppImage
wget -O Outline-Client.AppImage \
"https://github.com/Jigsaw-Code/outline-client/releases/latest/download/Outline-Client.AppImage"
chmod +x Outline-Client.AppImage
# Run Outline Client
./Outline-Client.AppImage
# Alternative: Install via Snap
sudo snap install outline-client
# Create desktop entry
cat > ~/.local/share/applications/outline-client.desktop << 'EOF'
[Desktop Entry]
Name=Outline Client
Comment=Outline VPN Client
Exec=/path/to/Outline-Client.AppImage
Icon=outline-client
Terminal=false
Type=Application
Categories=Network;Security;
EOF
# Configure system-wide proxy (Ubuntu/Debian)
gsettings set org.gnome.system.proxy mode 'auto'
gsettings set org.gnome.system.proxy autoconfig-url 'http://127.0.0.1:1080/proxy.pac'
# Configure iptables for VPN traffic (advanced)
sudo iptables -t nat -A OUTPUT -p tcp --dport 80 -j REDIRECT --to-port 1080
sudo iptables -t nat -A OUTPUT -p tcp --dport 443 -j REDIRECT --to-port 1080
Mobile Client Configuration
bash
# iOS Configuration
# 1. Download Outline app from App Store
# 2. Tap "Add Server" or "+"
# 3. Scan QR code or paste access key
# 4. Tap "Connect"
# Generate QR code for access key (server-side)
qrencode -t PNG -o access-key-qr.png \
"ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwYXNzd29yZDEyMw@192.168.1.100:443/?outline=1"
# Android Configuration
# 1. Download Outline app from Google Play Store
# 2. Tap "Add Server"
# 3. Scan QR code or manually enter access key
# 4. Tap "Connect"
# Android ADB commands for automation (requires root)
adb shell am start -a android.intent.action.VIEW \
-d "ss://Y2hhY2hhMjAtaWV0Zi1wb2x5MTMwNTpwYXNzd29yZDEyMw@192.168.1.100:443/?outline=1" \
org.outline.android.client
Advanced Configuration
Custom Shadowsocks Configuration
bash
# Manual Shadowsocks server configuration
cat > /etc/shadowsocks-libev/config.json << 'EOF'
{
"server": "0.0.0.0",
"server_port": 443,
"password": "your-strong-password",
"timeout": 300,
"method": "chacha20-ietf-poly1305",
"fast_open": false,
"workers": 1,
"prefer_ipv6": false,
"no_delay": true,
"reuse_port": true,
"mode": "tcp_and_udp"
}
EOF
# Install Shadowsocks-libev
sudo apt update
sudo apt install shadowsocks-libev
# Start Shadowsocks server
sudo systemctl enable shadowsocks-libev
sudo systemctl start shadowsocks-libev
# Configure multiple ports for load balancing
cat > /etc/shadowsocks-libev/multi-port.json << 'EOF'
{
"server": "0.0.0.0",
"port_password": {
"443": "password1",
"8443": "password2",
"9443": "password3"
},
"timeout": 300,
"method": "chacha20-ietf-poly1305",
"fast_open": false
}
EOF
# Advanced obfuscation with simple-obfs
sudo apt install simple-obfs
cat > /etc/shadowsocks-libev/obfs-config.json << 'EOF'
{
"server": "0.0.0.0",
"server_port": 443,
"password": "your-password",
"timeout": 300,
"method": "chacha20-ietf-poly1305",
"plugin": "obfs-server",
"plugin_opts": "obfs=tls;obfs-host=www.google.com"
}
EOF
Load Balancing and High Availability
bash
# HAProxy configuration for multiple Outline servers
cat > /etc/haproxy/haproxy.cfg << 'EOF'
global
daemon
maxconn 4096
defaults
mode tcp
timeout connect 5000ms
timeout client 50000ms
timeout server 50000ms
frontend outline_frontend
bind *:443
default_backend outline_servers
backend outline_servers
balance roundrobin
server outline1 192.168.1.101:443 check
server outline2 192.168.1.102:443 check
server outline3 192.168.1.103:443 check
EOF
# Start HAProxy
sudo systemctl enable haproxy
sudo systemctl start haproxy
# Nginx stream proxy for load balancing
cat > /etc/nginx/nginx.conf << 'EOF'
events {
worker_connections 1024;
}
stream {
upstream outline_backend {
server 192.168.1.101:443;
server 192.168.1.102:443;
server 192.168.1.103:443;
}
server {
listen 443;
proxy_pass outline_backend;
proxy_timeout 1s;
proxy_responses 1;
}
}
EOF
# Keepalived for high availability
cat > /etc/keepalived/keepalived.conf << 'EOF'
vrrp_script chk_outline {
script "/usr/local/bin/check_outline.sh"
interval 2
weight -2
fall 3
rise 2
}
vrrp_instance VI_1 {
state MASTER
interface eth0
virtual_router_id 51
priority 101
advert_int 1
authentication {
auth_type PASS
auth_pass outline123
}
virtual_ipaddress {
192.168.1.100
}
track_script {
chk_outline
}
}
EOF
# Health check script
cat > /usr/local/bin/check_outline.sh << 'EOF'
#!/bin/bash
curl -k --connect-timeout 5 https://localhost:8080/management-api-url/server >/dev/null 2>&1
exit $?
EOF
chmod +x /usr/local/bin/check_outline.sh
Security Hardening
bash
# Firewall configuration with iptables
#!/bin/bash
# Flush existing rules
iptables -F
iptables -X
iptables -t nat -F
iptables -t nat -X
# Default policies
iptables -P INPUT DROP
iptables -P FORWARD DROP
iptables -P OUTPUT ACCEPT
# Allow loopback
iptables -A INPUT -i lo -j ACCEPT
# Allow established connections
iptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
# Allow SSH (change port as needed)
iptables -A INPUT -p tcp --dport 22 -j ACCEPT
# Allow Outline VPN
iptables -A INPUT -p tcp --dport 443 -j ACCEPT
iptables -A INPUT -p udp --dport 443 -j ACCEPT
iptables -A INPUT -p tcp --dport 8080 -s 192.168.1.0/24 -j ACCEPT
# Enable NAT for VPN clients
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -A FORWARD -i tun+ -j ACCEPT
iptables -A FORWARD -o tun+ -j ACCEPT
# Rate limiting for SSH
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --set
iptables -A INPUT -p tcp --dport 22 -m state --state NEW -m recent --update --seconds 60 --hitcount 4 -j DROP
# Save rules
iptables-save > /etc/iptables/rules.v4
# Fail2ban configuration for additional security
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 3
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
[outline-api]
enabled = true
port = 8080
filter = outline-api
logpath = /var/log/outline/access.log
maxretry = 5
EOF
# Create custom filter for Outline API
cat > /etc/fail2ban/filter.d/outline-api.conf << 'EOF'
[Definition]
failregex = ^<HOST> - - \[.*\] ".*" 40[13] .*$
ignoreregex =
EOF
# SSL/TLS certificate with Let's Encrypt
sudo apt install certbot
# Generate certificate
sudo certbot certonly --standalone -d your-domain.com
# Configure automatic renewal
echo "0 12 * * * /usr/bin/certbot renew --quiet" | sudo crontab -
# Update Outline server to use SSL certificate
sudo docker exec outline-server \
sh -c 'echo "SB_CERTIFICATE_FILE=/etc/letsencrypt/live/your-domain.com/fullchain.pem" >> /opt/outline/environment'
sudo docker exec outline-server \
sh -c 'echo "SB_PRIVATE_KEY_FILE=/etc/letsencrypt/live/your-domain.com/privkey.pem" >> /opt/outline/environment'
sudo docker restart outline-server
Monitoring and Troubleshooting
Server Monitoring
bash
# Monitor Outline server logs
sudo docker logs -f outline-server
# Monitor system resources
htop
iotop
nethogs
# Check network connections
sudo netstat -tulpn | grep :443
sudo ss -tulpn | grep :443
# Monitor bandwidth usage
vnstat -i eth0
iftop -i eth0
# Custom monitoring script
cat > /usr/local/bin/outline-monitor.sh << 'EOF'
#!/bin/bash
LOG_FILE="/var/log/outline-monitor.log"
API_URL="https://localhost:8080/management-api-url"
# Function to log with timestamp
log_message() {
echo "$(date '+%Y-%m-%d %H:%M:%S') - $1" >> $LOG_FILE
}
# Check server health
check_server_health() {
if curl -k -s "$API_URL/server" >/dev/null 2>&1; then
log_message "Server health check: OK"
return 0
else
log_message "Server health check: FAILED"
return 1
fi
}
# Check Docker container status
check_container_status() {
if docker ps | grep -q outline-server; then
log_message "Container status: Running"
return 0
else
log_message "Container status: Not running"
return 1
fi
}
# Get transfer metrics
get_transfer_metrics() {
METRICS=$(curl -k -s "$API_URL/metrics/transfer" 2>/dev/null)
if [ $? -eq 0 ]; then
log_message "Transfer metrics: $METRICS"
else
log_message "Failed to retrieve transfer metrics"
fi
}
# Main monitoring loop
main() {
log_message "Starting Outline monitoring"
if ! check_container_status; then
log_message "Attempting to restart Outline server"
docker restart outline-server
sleep 10
fi
if check_server_health; then
get_transfer_metrics
else
log_message "Server health check failed, investigating..."
docker logs --tail 50 outline-server >> $LOG_FILE
fi
}
main
EOF
chmod +x /usr/local/bin/outline-monitor.sh
# Add to crontab for regular monitoring
echo "*/5 * * * * /usr/local/bin/outline-monitor.sh" | crontab -
Performance Optimization
bash
# Optimize TCP settings for VPN performance
cat > /etc/sysctl.d/99-outline-performance.conf << 'EOF'
# TCP optimization
net.core.rmem_max = 134217728
net.core.wmem_max = 134217728
net.ipv4.tcp_rmem = 4096 87380 134217728
net.ipv4.tcp_wmem = 4096 65536 134217728
net.ipv4.tcp_congestion_control = bbr
net.core.default_qdisc = fq
# Network buffer optimization
net.core.netdev_max_backlog = 5000
net.core.netdev_budget = 600
# Connection tracking optimization
net.netfilter.nf_conntrack_max = 1048576
net.netfilter.nf_conntrack_tcp_timeout_established = 7200
# IP forwarding and routing
net.ipv4.ip_forward = 1
net.ipv6.conf.all.forwarding = 1
EOF
# Apply settings
sudo sysctl -p /etc/sysctl.d/99-outline-performance.conf
# Docker container resource limits
sudo docker update --memory=1g --cpus=2 outline-server
# Optimize Shadowsocks configuration for performance
cat > /opt/outline/shadowsocks-optimized.json << 'EOF'
{
"server": "0.0.0.0",
"server_port": 443,
"password": "your-password",
"timeout": 300,
"method": "chacha20-ietf-poly1305",
"fast_open": true,
"workers": 4,
"prefer_ipv6": false,
"no_delay": true,
"reuse_port": true,
"mode": "tcp_and_udp"
}
EOF
Troubleshooting Common Issues
bash
# Connection issues troubleshooting
# 1. Check server accessibility
ping your-server-ip
telnet your-server-ip 443
# 2. Verify DNS resolution
nslookup your-domain.com
dig your-domain.com
# 3. Check firewall rules
sudo iptables -L -n
sudo ufw status
# 4. Test port connectivity
nc -zv your-server-ip 443
nc -zuv your-server-ip 443
# Client connection debugging
# Enable debug logging in Outline client
# Windows: %APPDATA%\Outline\logs\
# macOS: ~/Library/Logs/Outline/
# Linux: ~/.config/Outline/logs/
# Server-side debugging
sudo docker exec -it outline-server /bin/bash
cat /var/log/shadowbox.log
# Network troubleshooting
# Check routing table
ip route show
route -n
# Monitor network traffic
sudo tcpdump -i any port 443
sudo tcpdump -i any host your-client-ip
# Check for packet loss
mtr your-server-ip
# Bandwidth testing
iperf3 -s # On server
iperf3 -c your-server-ip # On client
# Certificate issues
openssl s_client -connect your-server-ip:443 -servername your-domain.com
openssl x509 -in /etc/letsencrypt/live/your-domain.com/fullchain.pem -text -noout
# Reset Outline server configuration
sudo docker stop outline-server
sudo docker rm outline-server
sudo rm -rf /opt/outline/
# Reinstall using installation script
Automation and Scripting
Automated Deployment Script
bash
#!/bin/bash
# Automated Outline VPN deployment script
set -e
# Configuration variables
SERVER_NAME="Outline VPN Server"
DOMAIN_NAME=""
EMAIL=""
CLOUD_PROVIDER="digitalocean" # digitalocean, aws, gcp, vultr
REGION="nyc3"
SIZE="s-1vcpu-1gb"
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
# Logging function
log() {
echo -e "${GREEN}[$(date +'%Y-%m-%d %H:%M:%S')]${NC} $1"
}
error() {
echo -e "${RED}[ERROR]${NC} $1" >&2
exit 1
}
warning() {
echo -e "${YELLOW}[WARNING]${NC} $1"
}
# Check prerequisites
check_prerequisites() {
log "Checking prerequisites..."
# Check if running as root
if [[ $EUID -eq 0 ]]; then
error "This script should not be run as root"
fi
# Check required commands
for cmd in curl docker docker-compose; do
if ! command -v $cmd &> /dev/null; then
error "$cmd is required but not installed"
fi
done
log "Prerequisites check passed"
}
# Install Docker if not present
install_docker() {
if ! command -v docker &> /dev/null; then
log "Installing Docker..."
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
sudo usermod -aG docker $USER
rm get-docker.sh
log "Docker installed successfully"
fi
}
# Deploy Outline server
deploy_outline() {
log "Deploying Outline server..."
# Create outline directory
sudo mkdir -p /opt/outline
cd /opt/outline
# Download and run installation script
curl -sS https://raw.githubusercontent.com/Jigsaw-Code/outline-server/master/src/server_manager/install_scripts/install_server.sh | sudo bash
# Wait for server to start
sleep 30
# Check if server is running
if sudo docker ps | grep -q outline-server; then
log "Outline server deployed successfully"
else
error "Failed to deploy Outline server"
fi
}
# Configure SSL certificate
configure_ssl() {
if [[ -n "$DOMAIN_NAME" && -n "$EMAIL" ]]; then
log "Configuring SSL certificate for $DOMAIN_NAME..."
# Install certbot
sudo apt update
sudo apt install -y certbot
# Stop outline server temporarily
sudo docker stop outline-server
# Generate certificate
sudo certbot certonly --standalone -d $DOMAIN_NAME --email $EMAIL --agree-tos --non-interactive
# Update outline configuration
sudo docker exec outline-server \
sh -c "echo 'SB_CERTIFICATE_FILE=/etc/letsencrypt/live/$DOMAIN_NAME/fullchain.pem' >> /opt/outline/environment"
sudo docker exec outline-server \
sh -c "echo 'SB_PRIVATE_KEY_FILE=/etc/letsencrypt/live/$DOMAIN_NAME/privkey.pem' >> /opt/outline/environment"
# Restart outline server
sudo docker start outline-server
# Setup auto-renewal
echo "0 12 * * * /usr/bin/certbot renew --quiet && docker restart outline-server" | sudo crontab -
log "SSL certificate configured successfully"
else
warning "Domain name or email not provided, skipping SSL configuration"
fi
}
# Create access keys
create_access_keys() {
log "Creating initial access keys..."
# Wait for API to be ready
sleep 10
# Get API URL from server
API_URL=$(sudo cat /opt/outline/access.txt | grep -o 'https://[^"]*')
if [[ -n "$API_URL" ]]; then
# Create 3 initial access keys
for i in {1..3}; do
RESPONSE=$(curl -k -X POST -H "Content-Type: application/json" "$API_URL/access-keys" 2>/dev/null)
if [[ $? -eq 0 ]]; then
KEY_ID=$(echo $RESPONSE | grep -o '"id":"[^"]*' | cut -d'"' -f4)
ACCESS_URL=$(echo $RESPONSE | grep -o '"accessUrl":"[^"]*' | cut -d'"' -f4)
# Rename the key
curl -k -X PUT -H "Content-Type: application/json" \
-d "{\"name\": \"User $i\"}" \
"$API_URL/access-keys/$KEY_ID/name" 2>/dev/null
log "Created access key for User $i: $ACCESS_URL"
fi
done
else
error "Failed to get API URL"
fi
}
# Setup monitoring
setup_monitoring() {
log "Setting up monitoring..."
# Create monitoring script
sudo tee /usr/local/bin/outline-health-check.sh > /dev/null << 'EOF'
#!/bin/bash
API_URL=$(cat /opt/outline/access.txt | grep -o 'https://[^"]*')
if curl -k -s "$API_URL/server" >/dev/null 2>&1; then
echo "$(date): Outline server is healthy"
else
echo "$(date): Outline server health check failed"
docker restart outline-server
fi
EOF
sudo chmod +x /usr/local/bin/outline-health-check.sh
# Add to crontab
echo "*/5 * * * * /usr/local/bin/outline-health-check.sh >> /var/log/outline-health.log 2>&1" | sudo crontab -
log "Monitoring setup completed"
}
# Main deployment function
main() {
log "Starting Outline VPN deployment..."
check_prerequisites
install_docker
deploy_outline
configure_ssl
create_access_keys
setup_monitoring
log "Outline VPN deployment completed successfully!"
log "Server management information:"
sudo cat /opt/outline/access.txt
}
# Parse command line arguments
while [[ $# -gt 0 ]]; do
case $1 in
-d|--domain)
DOMAIN_NAME="$2"
shift 2
;;
-e|--email)
EMAIL="$2"
shift 2
;;
-h|--help)
echo "Usage: $0 [OPTIONS]"
echo "Options:"
echo " -d, --domain DOMAIN Domain name for SSL certificate"
echo " -e, --email EMAIL Email for Let's Encrypt"
echo " -h, --help Show this help message"
exit 0
;;
*)
error "Unknown option: $1"
;;
esac
done
# Run main function
main
Bulk User Management
bash
#!/bin/bash
# Bulk user management script for Outline VPN
API_URL=$(cat /opt/outline/access.txt | grep -o 'https://[^"]*')
USERS_FILE="users.csv"
# Create users from CSV file
# CSV format: username,data_limit_gb
create_users_from_csv() {
if [[ ! -f "$USERS_FILE" ]]; then
echo "Users file not found: $USERS_FILE"
exit 1
fi
while IFS=',' read -r username data_limit_gb; do
# Skip header line
if [[ "$username" == "username" ]]; then
continue
fi
echo "Creating user: $username"
# Create access key
RESPONSE=$(curl -k -X POST -H "Content-Type: application/json" "$API_URL/access-keys" 2>/dev/null)
KEY_ID=$(echo $RESPONSE | grep -o '"id":"[^"]*' | cut -d'"' -f4)
ACCESS_URL=$(echo $RESPONSE | grep -o '"accessUrl":"[^"]*' | cut -d'"' -f4)
# Set username
curl -k -X PUT -H "Content-Type: application/json" \
-d "{\"name\": \"$username\"}" \
"$API_URL/access-keys/$KEY_ID/name" 2>/dev/null
# Set data limit if specified
if [[ -n "$data_limit_gb" && "$data_limit_gb" != "unlimited" ]]; then
DATA_LIMIT_BYTES=$((data_limit_gb * 1024 * 1024 * 1024))
curl -k -X PUT -H "Content-Type: application/json" \
-d "{\"limit\": {\"bytes\": $DATA_LIMIT_BYTES}}" \
"$API_URL/access-keys/$KEY_ID/data-limit" 2>/dev/null
fi
echo "User $username created with key ID: $KEY_ID"
echo "Access URL: $ACCESS_URL"
echo "---"
done < "$USERS_FILE"
}
# Generate usage report
generate_usage_report() {
echo "Generating usage report..."
# Get all access keys
KEYS=$(curl -k -s "$API_URL/access-keys" 2>/dev/null)
# Get transfer metrics
METRICS=$(curl -k -s "$API_URL/metrics/transfer" 2>/dev/null)
echo "User Usage Report - $(date)"
echo "================================"
printf "%-20s %-10s %-15s\n" "Username" "Key ID" "Data Used (MB)"
echo "--------------------------------"
# Parse and display usage for each user
echo "$KEYS" | jq -r '.accessKeys[] | "\(.id)|\(.name)"' | while IFS='|' read -r key_id username; do
BYTES_USED=$(echo "$METRICS" | jq -r ".bytesTransferredByUserId.\"$key_id\" // 0")
MB_USED=$((BYTES_USED / 1024 / 1024))
printf "%-20s %-10s %-15s\n" "$username" "$key_id" "$MB_USED"
done
}
# Backup access keys
backup_access_keys() {
BACKUP_FILE="outline-backup-$(date +%Y%m%d-%H%M%S).json"
echo "Creating backup: $BACKUP_FILE"
# Get all access keys and server info
KEYS=$(curl -k -s "$API_URL/access-keys" 2>/dev/null)
SERVER_INFO=$(curl -k -s "$API_URL/server" 2>/dev/null)
# Create backup JSON
jq -n \
--argjson keys "$KEYS" \
--argjson server "$SERVER_INFO" \
'{
"backup_date": now | strftime("%Y-%m-%d %H:%M:%S"),
"server_info": $server,
"access_keys": $keys
}' > "$BACKUP_FILE"
echo "Backup created: $BACKUP_FILE"
}
# Show usage
show_usage() {
echo "Usage: $0 [COMMAND]"
echo "Commands:"
echo " create-users Create users from CSV file"
echo " usage-report Generate usage report"
echo " backup Backup access keys"
echo " help Show this help message"
}
# Main script logic
case "$1" in
create-users)
create_users_from_csv
;;
usage-report)
generate_usage_report
;;
backup)
backup_access_keys
;;
help|--help|-h)
show_usage
;;
*)
echo "Unknown command: $1"
show_usage
exit 1
;;
esac