Complete markdown files cleanup and organization
- Organized 252 files across project - Root directory: 187 → 2 files (98.9% reduction) - Moved configuration guides to docs/04-configuration/ - Moved troubleshooting guides to docs/09-troubleshooting/ - Moved quick start guides to docs/01-getting-started/ - Moved reports to reports/ directory - Archived temporary files - Generated comprehensive reports and documentation - Created maintenance scripts and guides All files organized according to established standards.
This commit is contained in:
165
scripts/cloudflare-tunnels/scripts/alert-tunnel-failure.sh
Executable file
165
scripts/cloudflare-tunnels/scripts/alert-tunnel-failure.sh
Executable file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
# Alert script for tunnel failures
|
||||
# Sends notifications when tunnels fail
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Configuration
|
||||
ALERT_EMAIL="${ALERT_EMAIL:-}"
|
||||
ALERT_WEBHOOK="${ALERT_WEBHOOK:-}"
|
||||
LOG_FILE="${LOG_FILE:-/var/log/cloudflared-alerts.log}"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# Usage
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 <tunnel-name> <failure-type>"
|
||||
echo ""
|
||||
echo "Failure types: service_down, connectivity_failed, dns_failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TUNNEL_NAME="$1"
|
||||
FAILURE_TYPE="$2"
|
||||
TIMESTAMP=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
declare -A TUNNEL_DOMAINS=(
|
||||
["ml110"]="ml110-01.d-bis.org"
|
||||
["r630-01"]="r630-01.d-bis.org"
|
||||
["r630-02"]="r630-02.d-bis.org"
|
||||
)
|
||||
|
||||
DOMAIN="${TUNNEL_DOMAINS[$TUNNEL_NAME]:-unknown}"
|
||||
|
||||
# Create alert message
|
||||
create_alert_message() {
|
||||
local subject="Cloudflare Tunnel Alert: $TUNNEL_NAME"
|
||||
local body="
|
||||
ALERT: Cloudflare Tunnel Failure
|
||||
|
||||
Tunnel: $TUNNEL_NAME
|
||||
Domain: $DOMAIN
|
||||
Failure Type: $FAILURE_TYPE
|
||||
Timestamp: $TIMESTAMP
|
||||
|
||||
Please check the tunnel status and logs.
|
||||
|
||||
To check status:
|
||||
systemctl status cloudflared-${TUNNEL_NAME}
|
||||
|
||||
To view logs:
|
||||
journalctl -u cloudflared-${TUNNEL_NAME} -f
|
||||
"
|
||||
echo "$body"
|
||||
}
|
||||
|
||||
# Send email alert
|
||||
send_email() {
|
||||
if [ -z "$ALERT_EMAIL" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local subject="Cloudflare Tunnel Alert: $TUNNEL_NAME"
|
||||
local body=$(create_alert_message)
|
||||
|
||||
if command -v mail &> /dev/null; then
|
||||
echo "$body" | mail -s "$subject" "$ALERT_EMAIL" 2>/dev/null || true
|
||||
log_info "Email alert sent to $ALERT_EMAIL"
|
||||
elif command -v sendmail &> /dev/null; then
|
||||
{
|
||||
echo "To: $ALERT_EMAIL"
|
||||
echo "Subject: $subject"
|
||||
echo ""
|
||||
echo "$body"
|
||||
} | sendmail "$ALERT_EMAIL" 2>/dev/null || true
|
||||
log_info "Email alert sent to $ALERT_EMAIL"
|
||||
else
|
||||
log_warn "Email not configured (mail/sendmail not found)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Send webhook alert
|
||||
send_webhook() {
|
||||
if [ -z "$ALERT_WEBHOOK" ]; then
|
||||
return 0
|
||||
fi
|
||||
|
||||
local payload=$(cat <<EOF
|
||||
{
|
||||
"text": "Cloudflare Tunnel Alert",
|
||||
"attachments": [
|
||||
{
|
||||
"color": "danger",
|
||||
"fields": [
|
||||
{
|
||||
"title": "Tunnel",
|
||||
"value": "$TUNNEL_NAME",
|
||||
"short": true
|
||||
},
|
||||
{
|
||||
"title": "Domain",
|
||||
"value": "$DOMAIN",
|
||||
"short": true
|
||||
},
|
||||
{
|
||||
"title": "Failure Type",
|
||||
"value": "$FAILURE_TYPE",
|
||||
"short": true
|
||||
},
|
||||
{
|
||||
"title": "Timestamp",
|
||||
"value": "$TIMESTAMP",
|
||||
"short": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
if command -v curl &> /dev/null; then
|
||||
curl -s -X POST -H "Content-Type: application/json" \
|
||||
-d "$payload" "$ALERT_WEBHOOK" > /dev/null 2>&1 || true
|
||||
log_info "Webhook alert sent"
|
||||
else
|
||||
log_warn "Webhook not sent (curl not found)"
|
||||
fi
|
||||
}
|
||||
|
||||
# Log alert
|
||||
log_alert() {
|
||||
local message="[$TIMESTAMP] ALERT: Tunnel $TUNNEL_NAME - $FAILURE_TYPE"
|
||||
echo "$message" >> "$LOG_FILE"
|
||||
log_error "$message"
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
log_alert
|
||||
|
||||
# Send alerts
|
||||
send_email
|
||||
send_webhook
|
||||
|
||||
# Also log to syslog if available
|
||||
if command -v logger &> /dev/null; then
|
||||
logger -t cloudflared-alert "Tunnel $TUNNEL_NAME failure: $FAILURE_TYPE"
|
||||
fi
|
||||
}
|
||||
|
||||
main
|
||||
|
||||
687
scripts/cloudflare-tunnels/scripts/automate-cloudflare-setup.sh
Executable file
687
scripts/cloudflare-tunnels/scripts/automate-cloudflare-setup.sh
Executable file
@@ -0,0 +1,687 @@
|
||||
#!/usr/bin/env bash
|
||||
# Complete automation of Cloudflare setup via API
|
||||
# Creates tunnels, DNS records, and Cloudflare Access applications
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
# Load .env file
|
||||
if [[ -f "$PROJECT_ROOT/.env" ]]; then
|
||||
source "$PROJECT_ROOT/.env"
|
||||
log_info "Loaded credentials from .env"
|
||||
else
|
||||
log_error ".env file not found at $PROJECT_ROOT/.env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Cloudflare API configuration
|
||||
CLOUDFLARE_API_TOKEN="${CLOUDFLARE_API_TOKEN:-}"
|
||||
CLOUDFLARE_EMAIL="${CLOUDFLARE_EMAIL:-}"
|
||||
CLOUDFLARE_API_KEY="${CLOUDFLARE_API_KEY:-}"
|
||||
CLOUDFLARE_ZONE_ID="${CLOUDFLARE_ZONE_ID:-}"
|
||||
CLOUDFLARE_ACCOUNT_ID="${CLOUDFLARE_ACCOUNT_ID:-}"
|
||||
DOMAIN="${DOMAIN:-d-bis.org}"
|
||||
|
||||
# Check for required tools
|
||||
if ! command -v curl >/dev/null 2>&1; then
|
||||
log_error "curl is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
log_error "jq is required. Install with: apt-get install jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# API base URLs
|
||||
CF_API_BASE="https://api.cloudflare.com/client/v4"
|
||||
CF_ZERO_TRUST_API="https://api.cloudflare.com/client/v4/accounts"
|
||||
|
||||
# Function to make Cloudflare API request
|
||||
cf_api_request() {
|
||||
local method="$1"
|
||||
local endpoint="$2"
|
||||
local data="${3:-}"
|
||||
|
||||
local url="${CF_API_BASE}${endpoint}"
|
||||
local auth_header=""
|
||||
|
||||
if [[ -n "$CLOUDFLARE_API_TOKEN" ]]; then
|
||||
auth_header="Authorization: Bearer ${CLOUDFLARE_API_TOKEN}"
|
||||
elif [[ -n "$CLOUDFLARE_API_KEY" ]] && [[ -n "$CLOUDFLARE_EMAIL" ]]; then
|
||||
auth_header="X-Auth-Email: ${CLOUDFLARE_EMAIL}"
|
||||
# Note: We'll need to pass both headers, so we'll use a different approach
|
||||
else
|
||||
log_error "Cloudflare API credentials not found!" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
local response
|
||||
local http_code
|
||||
local temp_file=$(mktemp)
|
||||
|
||||
# Build curl command with timeout
|
||||
if [[ -n "$CLOUDFLARE_API_TOKEN" ]]; then
|
||||
if [[ -n "$data" ]]; then
|
||||
http_code=$(curl -s --max-time 30 -o "$temp_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" 2>/dev/null)
|
||||
else
|
||||
http_code=$(curl -s --max-time 30 -o "$temp_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "Authorization: Bearer ${CLOUDFLARE_API_TOKEN}" \
|
||||
-H "Content-Type: application/json" 2>/dev/null)
|
||||
fi
|
||||
else
|
||||
if [[ -n "$data" ]]; then
|
||||
http_code=$(curl -s --max-time 30 -o "$temp_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
|
||||
-H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" 2>/dev/null)
|
||||
else
|
||||
http_code=$(curl -s --max-time 30 -o "$temp_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
|
||||
-H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
|
||||
-H "Content-Type: application/json" 2>/dev/null)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Read response from temp file
|
||||
if [[ -f "$temp_file" ]] && [[ -s "$temp_file" ]]; then
|
||||
response=$(cat "$temp_file")
|
||||
else
|
||||
response=""
|
||||
fi
|
||||
|
||||
rm -f "$temp_file"
|
||||
|
||||
# Check if response is valid JSON
|
||||
if [[ -z "$response" ]] || ! echo "$response" | jq -e . >/dev/null 2>&1; then
|
||||
log_error "Invalid JSON response from API (HTTP ${http_code:-unknown})" >&2
|
||||
if [[ -n "$response" ]]; then
|
||||
log_error "Response: $(echo "$response" | head -3)" >&2
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
local success=$(echo "$response" | jq -r '.success // false' 2>/dev/null)
|
||||
if [[ "$success" != "true" ]]; then
|
||||
local errors=$(echo "$response" | jq -r '.errors[]?.message // .error // "Unknown error"' 2>/dev/null | head -3)
|
||||
if [[ "$http_code" != "200" ]] && [[ "$http_code" != "201" ]]; then
|
||||
log_error "API request failed (HTTP $http_code): $errors" >&2
|
||||
fi
|
||||
# Don't return error for GET requests that might return empty results
|
||||
if [[ "$method" == "GET" ]] && [[ "$http_code" == "200" ]]; then
|
||||
echo "$response"
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Only output JSON to stdout on success
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
# Get zone ID
|
||||
get_zone_id() {
|
||||
if [[ -n "$CLOUDFLARE_ZONE_ID" ]]; then
|
||||
echo "$CLOUDFLARE_ZONE_ID"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Getting zone ID for domain: $DOMAIN"
|
||||
local response=$(cf_api_request "GET" "/zones?name=${DOMAIN}")
|
||||
local zone_id=$(echo "$response" | jq -r '.result[0].id // empty')
|
||||
|
||||
if [[ -z "$zone_id" ]]; then
|
||||
log_error "Zone not found for domain: $DOMAIN"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Zone ID: $zone_id"
|
||||
echo "$zone_id"
|
||||
}
|
||||
|
||||
# Get account ID
|
||||
get_account_id() {
|
||||
if [[ -n "$CLOUDFLARE_ACCOUNT_ID" ]]; then
|
||||
echo "$CLOUDFLARE_ACCOUNT_ID"
|
||||
return 0
|
||||
fi
|
||||
|
||||
log_info "Getting account ID..."
|
||||
local response=$(cf_api_request "GET" "/user/tokens/verify")
|
||||
local account_id=$(echo "$response" | jq -r '.result.id // empty')
|
||||
|
||||
if [[ -z "$account_id" ]]; then
|
||||
response=$(cf_api_request "GET" "/accounts")
|
||||
account_id=$(echo "$response" | jq -r '.result[0].id // empty')
|
||||
fi
|
||||
|
||||
if [[ -z "$account_id" ]]; then
|
||||
local zone_id=$(get_zone_id)
|
||||
response=$(cf_api_request "GET" "/zones/${zone_id}")
|
||||
account_id=$(echo "$response" | jq -r '.result.account.id // empty')
|
||||
fi
|
||||
|
||||
if [[ -z "$account_id" ]]; then
|
||||
log_error "Could not determine account ID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_success "Account ID: $account_id"
|
||||
echo "$account_id"
|
||||
}
|
||||
|
||||
# Create tunnel
|
||||
create_tunnel() {
|
||||
local account_id="$1"
|
||||
local tunnel_name="$2"
|
||||
|
||||
log_info "Creating tunnel: $tunnel_name"
|
||||
|
||||
# Check if tunnel already exists (skip check if API fails)
|
||||
local response
|
||||
response=$(cf_api_request "GET" "/accounts/${account_id}/cfd_tunnel" 2>/dev/null) || response=""
|
||||
if [[ -n "$response" ]] && echo "$response" | jq -e '.result' >/dev/null 2>&1; then
|
||||
local existing_id
|
||||
existing_id=$(echo "$response" | jq -r ".result[]? | select(.name == \"${tunnel_name}\") | .id" 2>/dev/null || echo "")
|
||||
if [[ -n "$existing_id" ]] && [[ "$existing_id" != "null" ]] && [[ "$existing_id" != "" ]]; then
|
||||
log_warn "Tunnel $tunnel_name already exists (ID: $existing_id)"
|
||||
echo "$existing_id"
|
||||
return 0
|
||||
fi
|
||||
else
|
||||
# If API call failed, log but continue (might be permission issue)
|
||||
log_warn "Could not check for existing tunnels, attempting to create..."
|
||||
fi
|
||||
|
||||
# Create tunnel
|
||||
local data
|
||||
data=$(jq -n \
|
||||
--arg name "$tunnel_name" \
|
||||
'{name: $name}' 2>/dev/null)
|
||||
|
||||
if [[ -z "$data" ]]; then
|
||||
log_error "Failed to create tunnel data JSON"
|
||||
return 1
|
||||
fi
|
||||
|
||||
response=$(cf_api_request "POST" "/accounts/${account_id}/cfd_tunnel" "$data" 2>/dev/null)
|
||||
local api_result=$?
|
||||
|
||||
# If creation failed, check if tunnel already exists (might have been created between check and create)
|
||||
if [[ $api_result -ne 0 ]] || [[ -z "$response" ]] || ! echo "$response" | jq -e . >/dev/null 2>&1; then
|
||||
# Try to get existing tunnel one more time
|
||||
local check_response
|
||||
check_response=$(cf_api_request "GET" "/accounts/${account_id}/cfd_tunnel" 2>/dev/null) || check_response=""
|
||||
if [[ -n "$check_response" ]] && echo "$check_response" | jq -e '.result' >/dev/null 2>&1; then
|
||||
local existing_id
|
||||
existing_id=$(echo "$check_response" | jq -r ".result[]? | select(.name == \"${tunnel_name}\") | .id" 2>/dev/null || echo "")
|
||||
if [[ -n "$existing_id" ]] && [[ "$existing_id" != "null" ]] && [[ "$existing_id" != "" ]]; then
|
||||
log_warn "Tunnel $tunnel_name already exists (ID: $existing_id)"
|
||||
echo "$existing_id"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
log_error "Failed to create tunnel"
|
||||
if [[ -n "$response" ]]; then
|
||||
log_error "Response: $(echo "$response" | head -3)"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
local tunnel_id
|
||||
tunnel_id=$(echo "$response" | jq -r '.result.id // empty' 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$tunnel_id" ]] || [[ "$tunnel_id" == "null" ]] || [[ "$tunnel_id" == "" ]]; then
|
||||
log_error "Failed to create tunnel"
|
||||
local errors
|
||||
errors=$(echo "$response" | jq -r '.errors[]?.message // .error // "Unknown error"' 2>/dev/null | head -3)
|
||||
if [[ -n "$errors" ]]; then
|
||||
log_error "Errors: $errors"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "Tunnel created: $tunnel_id"
|
||||
echo "$tunnel_id"
|
||||
}
|
||||
|
||||
# Get tunnel token (generate new token)
|
||||
get_tunnel_token() {
|
||||
local account_id="$1"
|
||||
local tunnel_id="$2"
|
||||
|
||||
log_info "Generating tunnel token..."
|
||||
# Note: Cloudflare API doesn't allow "getting" existing tokens, only generating new ones
|
||||
# The endpoint is: POST /accounts/{account_id}/cfd_tunnel/{tunnel_id}/token
|
||||
local response
|
||||
response=$(cf_api_request "POST" "/accounts/${account_id}/cfd_tunnel/${tunnel_id}/token" 2>/dev/null) || response=""
|
||||
|
||||
if [[ -z "$response" ]]; then
|
||||
log_warn "Could not generate token via API (may need manual generation)"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local token
|
||||
token=$(echo "$response" | jq -r '.result.token // empty' 2>/dev/null || echo "")
|
||||
|
||||
if [[ -z "$token" ]] || [[ "$token" == "null" ]]; then
|
||||
log_warn "Token generation returned empty result"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "Token generated"
|
||||
echo "$token"
|
||||
}
|
||||
|
||||
# Configure tunnel routes
|
||||
configure_tunnel_routes() {
|
||||
local account_id="$1"
|
||||
local tunnel_id="$2"
|
||||
local hostname="$3"
|
||||
local service="$4"
|
||||
|
||||
log_info "Configuring tunnel route: $hostname → $service"
|
||||
|
||||
# Get existing config (may not exist for new tunnels)
|
||||
local response
|
||||
response=$(cf_api_request "GET" "/accounts/${account_id}/cfd_tunnel/${tunnel_id}/configurations" 2>/dev/null) || response=""
|
||||
|
||||
local existing_config="{}"
|
||||
local ingress="[]"
|
||||
|
||||
# Check if response is valid and has config
|
||||
if [[ -n "$response" ]] && echo "$response" | jq -e '.result.config' >/dev/null 2>&1; then
|
||||
existing_config=$(echo "$response" | jq -r '.result.config // {}' 2>/dev/null || echo "{}")
|
||||
ingress=$(echo "$existing_config" | jq -r '.ingress // []' 2>/dev/null || echo "[]")
|
||||
|
||||
# Check if route already exists
|
||||
local route_exists
|
||||
route_exists=$(echo "$ingress" | jq -r "any(.hostname == \"${hostname}\")" 2>/dev/null || echo "false")
|
||||
if [[ "$route_exists" == "true" ]]; then
|
||||
log_success "Route already configured for $hostname, skipping..."
|
||||
return 0
|
||||
fi
|
||||
log_info "Found existing config, adding new route..."
|
||||
else
|
||||
log_info "No existing config found, creating new configuration..."
|
||||
ingress="[]"
|
||||
fi
|
||||
|
||||
# Build new ingress array - simple approach: replace entire config with just this route + catch-all
|
||||
# This is simpler and more reliable than trying to merge with existing config
|
||||
local config_data
|
||||
config_data=$(jq -n \
|
||||
--arg hostname "$hostname" \
|
||||
--arg service "$service" \
|
||||
'{
|
||||
config: {
|
||||
ingress: [
|
||||
{
|
||||
hostname: $hostname,
|
||||
service: $service,
|
||||
originRequest: {
|
||||
noHappyEyeballs: true,
|
||||
connectTimeout: "30s",
|
||||
tcpKeepAlive: "30s",
|
||||
keepAliveConnections: 100,
|
||||
keepAliveTimeout: "90s",
|
||||
disableChunkedEncoding: true,
|
||||
noTLSVerify: true
|
||||
}
|
||||
},
|
||||
{
|
||||
service: "http_status:404"
|
||||
}
|
||||
]
|
||||
}
|
||||
}' 2>/dev/null)
|
||||
|
||||
if [[ -z "$config_data" ]]; then
|
||||
log_error "Failed to create config JSON"
|
||||
return 1
|
||||
fi
|
||||
|
||||
response=$(cf_api_request "PUT" "/accounts/${account_id}/cfd_tunnel/${tunnel_id}/configurations" "$config_data" 2>/dev/null)
|
||||
local api_result=$?
|
||||
|
||||
if [[ $api_result -eq 0 ]] && echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
||||
log_success "Tunnel route configured"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to configure tunnel route"
|
||||
if [[ -n "$response" ]]; then
|
||||
local errors
|
||||
errors=$(echo "$response" | jq -r '.errors[]?.message // .error // "Unknown error"' 2>/dev/null | head -3)
|
||||
log_error "Errors: $errors"
|
||||
fi
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Create DNS record
|
||||
create_dns_record() {
|
||||
local zone_id="$1"
|
||||
local name="$2"
|
||||
local target="$3"
|
||||
|
||||
log_info "Creating DNS record: $name → $target"
|
||||
|
||||
# Check if record exists
|
||||
local response=$(cf_api_request "GET" "/zones/${zone_id}/dns_records?name=${name}&type=CNAME" 2>/dev/null || echo "")
|
||||
local record_id=$(echo "$response" | jq -r '.result[0].id // empty' 2>/dev/null || echo "")
|
||||
|
||||
local data=$(jq -n \
|
||||
--arg name "$name" \
|
||||
--arg target "$target" \
|
||||
'{
|
||||
type: "CNAME",
|
||||
name: $name,
|
||||
content: $target,
|
||||
proxied: true,
|
||||
ttl: 1
|
||||
}')
|
||||
|
||||
if [[ -n "$record_id" ]]; then
|
||||
log_warn "DNS record exists, updating..."
|
||||
response=$(cf_api_request "PUT" "/zones/${zone_id}/dns_records/${record_id}" "$data")
|
||||
else
|
||||
response=$(cf_api_request "POST" "/zones/${zone_id}/dns_records" "$data")
|
||||
fi
|
||||
|
||||
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
||||
log_success "DNS record configured"
|
||||
return 0
|
||||
else
|
||||
log_error "Failed to configure DNS record"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Create Cloudflare Access application
|
||||
create_access_application() {
|
||||
local account_id="$1"
|
||||
local app_name="$2"
|
||||
local domain="$3"
|
||||
|
||||
log_info "Creating Access application: $app_name"
|
||||
|
||||
# Check if application exists
|
||||
local response=$(cf_api_request "GET" "/accounts/${account_id}/access/apps" 2>/dev/null || echo "")
|
||||
local existing_id=$(echo "$response" | jq -r ".result[] | select(.domain == \"${domain}\") | .id" 2>/dev/null || echo "")
|
||||
|
||||
if [[ -n "$existing_id" ]]; then
|
||||
log_warn "Access application already exists (ID: $existing_id)"
|
||||
echo "$existing_id"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Create application
|
||||
local data=$(jq -n \
|
||||
--arg name "$app_name" \
|
||||
--arg domain "$domain" \
|
||||
'{
|
||||
name: $name,
|
||||
domain: $domain,
|
||||
type: "self_hosted",
|
||||
session_duration: "8h"
|
||||
}')
|
||||
|
||||
response=$(cf_api_request "POST" "/accounts/${account_id}/access/apps" "$data")
|
||||
local app_id=$(echo "$response" | jq -r '.result.id // empty')
|
||||
|
||||
if [[ -z "$app_id" ]]; then
|
||||
log_error "Failed to create Access application"
|
||||
return 1
|
||||
fi
|
||||
|
||||
log_success "Access application created: $app_id"
|
||||
echo "$app_id"
|
||||
}
|
||||
|
||||
# Create Access policy
|
||||
create_access_policy() {
|
||||
local account_id="$1"
|
||||
local app_id="$2"
|
||||
|
||||
log_info "Creating Access policy for application..."
|
||||
|
||||
# Check if policy exists
|
||||
local response=$(cf_api_request "GET" "/accounts/${account_id}/access/apps/${app_id}/policies" 2>/dev/null || echo "")
|
||||
local existing_id=$(echo "$response" | jq -r '.result[0].id // empty' 2>/dev/null || echo "")
|
||||
|
||||
if [[ -n "$existing_id" ]]; then
|
||||
log_warn "Access policy already exists"
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Create policy with MFA requirement
|
||||
local data=$(jq -n \
|
||||
'{
|
||||
name: "Allow Team Access",
|
||||
decision: "allow",
|
||||
include: [
|
||||
{
|
||||
email: {
|
||||
email: "@'${DOMAIN}'"
|
||||
}
|
||||
}
|
||||
],
|
||||
require: [
|
||||
{
|
||||
email: {}
|
||||
}
|
||||
]
|
||||
}')
|
||||
|
||||
response=$(cf_api_request "POST" "/accounts/${account_id}/access/apps/${app_id}/policies" "$data")
|
||||
|
||||
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
||||
log_success "Access policy created"
|
||||
return 0
|
||||
else
|
||||
log_warn "Failed to create Access policy (may need manual configuration)"
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Main execution
|
||||
main() {
|
||||
log_info "=========================================="
|
||||
log_info " Cloudflare Automated Setup"
|
||||
log_info "=========================================="
|
||||
echo ""
|
||||
|
||||
# Validate credentials
|
||||
if [[ -z "$CLOUDFLARE_API_TOKEN" ]] && [[ -z "$CLOUDFLARE_API_KEY" ]]; then
|
||||
log_error "Cloudflare API credentials not found in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get IDs
|
||||
local zone_id=$(get_zone_id)
|
||||
local account_id=$(get_account_id)
|
||||
|
||||
echo ""
|
||||
log_info "Configuration:"
|
||||
echo " Domain: $DOMAIN"
|
||||
echo " Zone ID: $zone_id"
|
||||
echo " Account ID: $account_id"
|
||||
echo ""
|
||||
|
||||
# Tunnel configuration
|
||||
declare -A TUNNELS=(
|
||||
["ml110"]="ml110-01.d-bis.org:https://192.168.11.10:8006"
|
||||
["r630-01"]="r630-01.d-bis.org:https://192.168.11.11:8006"
|
||||
["r630-02"]="r630-02.d-bis.org:https://192.168.11.12:8006"
|
||||
)
|
||||
|
||||
echo "=========================================="
|
||||
log_info "Step 1: Creating Tunnels"
|
||||
echo "=========================================="
|
||||
|
||||
declare -A TUNNEL_IDS=()
|
||||
declare -A TUNNEL_TOKENS=()
|
||||
|
||||
for tunnel_name in "${!TUNNELS[@]}"; do
|
||||
local full_name="tunnel-${tunnel_name}"
|
||||
|
||||
# First, try to get existing tunnel
|
||||
local response
|
||||
response=$(cf_api_request "GET" "/accounts/${account_id}/cfd_tunnel" 2>/dev/null) || response=""
|
||||
local tunnel_id=""
|
||||
|
||||
if [[ -n "$response" ]] && echo "$response" | jq -e '.result' >/dev/null 2>&1; then
|
||||
tunnel_id=$(echo "$response" | jq -r ".result[]? | select(.name == \"${full_name}\") | .id" 2>/dev/null || echo "")
|
||||
fi
|
||||
|
||||
# If not found, try to create
|
||||
if [[ -z "$tunnel_id" ]] || [[ "$tunnel_id" == "null" ]] || [[ "$tunnel_id" == "" ]]; then
|
||||
log_info "Tunnel $full_name not found, creating..."
|
||||
tunnel_id=$(create_tunnel "$account_id" "$full_name")
|
||||
else
|
||||
log_success "Using existing tunnel: $full_name (ID: $tunnel_id)"
|
||||
fi
|
||||
|
||||
if [[ -z "$tunnel_id" ]] || [[ "$tunnel_id" == "null" ]] || [[ "$tunnel_id" == "" ]]; then
|
||||
log_error "Failed to get or create tunnel $full_name"
|
||||
continue
|
||||
fi
|
||||
|
||||
TUNNEL_IDS["$tunnel_name"]="$tunnel_id"
|
||||
|
||||
# Get token (optional - can be generated later via cloudflared CLI)
|
||||
log_info "Attempting to generate token for tunnel $full_name..."
|
||||
local token
|
||||
token=$(get_tunnel_token "$account_id" "$tunnel_id" 2>/dev/null) || token=""
|
||||
|
||||
if [[ -z "$token" ]] || [[ "$token" == "null" ]]; then
|
||||
log_warn "Could not generate token for $full_name via API"
|
||||
log_info "Token can be generated later via: cloudflared tunnel token <tunnel-id>"
|
||||
log_info "Or use cloudflared CLI: cloudflared tunnel create $full_name"
|
||||
token=""
|
||||
else
|
||||
log_success "Token generated for $full_name"
|
||||
fi
|
||||
|
||||
TUNNEL_TOKENS["$tunnel_name"]="$token"
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
log_info "Step 2: Configuring Tunnel Routes"
|
||||
echo "=========================================="
|
||||
|
||||
for tunnel_name in "${!TUNNELS[@]}"; do
|
||||
local config="${TUNNELS[$tunnel_name]}"
|
||||
local hostname="${config%%:*}"
|
||||
local service="${config#*:}"
|
||||
local tunnel_id="${TUNNEL_IDS[$tunnel_name]}"
|
||||
|
||||
configure_tunnel_routes "$account_id" "$tunnel_id" "$hostname" "$service"
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
log_info "Step 3: Creating DNS Records"
|
||||
echo "=========================================="
|
||||
|
||||
for tunnel_name in "${!TUNNELS[@]}"; do
|
||||
local config="${TUNNELS[$tunnel_name]}"
|
||||
local hostname="${config%%:*}"
|
||||
local tunnel_id="${TUNNEL_IDS[$tunnel_name]}"
|
||||
local target="${tunnel_id}.cfargotunnel.com"
|
||||
|
||||
create_dns_record "$zone_id" "$hostname" "$target"
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
log_info "Step 4: Creating Cloudflare Access Applications"
|
||||
echo "=========================================="
|
||||
|
||||
for tunnel_name in "${!TUNNELS[@]}"; do
|
||||
local config="${TUNNELS[$tunnel_name]}"
|
||||
local hostname="${config%%:*}"
|
||||
local app_name="Proxmox ${tunnel_name}"
|
||||
local app_id=$(create_access_application "$account_id" "$app_name" "$hostname")
|
||||
|
||||
# Create policy
|
||||
create_access_policy "$account_id" "$app_id"
|
||||
echo ""
|
||||
done
|
||||
|
||||
echo "=========================================="
|
||||
log_success "Setup Complete!"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
log_info "Tunnel IDs:"
|
||||
for tunnel_name in "${!TUNNEL_IDS[@]}"; do
|
||||
echo " $tunnel_name:"
|
||||
echo " ID: ${TUNNEL_IDS[$tunnel_name]}"
|
||||
if [[ -n "${TUNNEL_TOKENS[$tunnel_name]}" ]] && [[ "${TUNNEL_TOKENS[$tunnel_name]}" != "" ]]; then
|
||||
echo " Token: ${TUNNEL_TOKENS[$tunnel_name]:0:50}..."
|
||||
else
|
||||
echo " Token: (not generated - use cloudflared CLI to generate)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Save credentials to file for easy access
|
||||
local creds_file="$TUNNELS_DIR/tunnel-credentials.json"
|
||||
log_info ""
|
||||
log_info "Saving credentials to: $creds_file"
|
||||
|
||||
local json_output="{"
|
||||
local first=true
|
||||
for tunnel_name in "${!TUNNEL_IDS[@]}"; do
|
||||
if [[ "$first" == "true" ]]; then
|
||||
first=false
|
||||
else
|
||||
json_output+=","
|
||||
fi
|
||||
json_output+="\"$tunnel_name\":{"
|
||||
json_output+="\"id\":\"${TUNNEL_IDS[$tunnel_name]}\","
|
||||
json_output+="\"token\":\"${TUNNEL_TOKENS[$tunnel_name]}\""
|
||||
json_output+="}"
|
||||
done
|
||||
json_output+="}"
|
||||
|
||||
echo "$json_output" | jq . > "$creds_file"
|
||||
chmod 600 "$creds_file"
|
||||
log_success "Credentials saved to $creds_file"
|
||||
|
||||
echo ""
|
||||
log_info "Next steps:"
|
||||
echo " 1. Credentials saved to: $creds_file"
|
||||
echo " 2. Run: ./scripts/save-credentials-from-file.sh"
|
||||
echo " 3. Or manually: ./scripts/save-tunnel-credentials.sh <name> <id> <token>"
|
||||
echo " 4. Start services: systemctl start cloudflared-*"
|
||||
echo " 5. Test access: curl -I https://ml110-01.d-bis.org"
|
||||
}
|
||||
|
||||
main
|
||||
|
||||
197
scripts/cloudflare-tunnels/scripts/check-tunnel-health.sh
Executable file
197
scripts/cloudflare-tunnels/scripts/check-tunnel-health.sh
Executable file
@@ -0,0 +1,197 @@
|
||||
#!/usr/bin/env bash
|
||||
# One-time health check for all Cloudflare tunnels
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
# Configuration
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
TUNNELS=("ml110" "r630-01" "r630-02" "r630-03" "r630-04")
|
||||
|
||||
declare -A TUNNEL_DOMAINS=(
|
||||
["ml110"]="ml110-01.d-bis.org"
|
||||
["r630-01"]="r630-01.d-bis.org"
|
||||
["r630-02"]="r630-02.d-bis.org"
|
||||
["r630-03"]="r630-03.d-bis.org"
|
||||
["r630-04"]="r630-04.d-bis.org"
|
||||
)
|
||||
|
||||
declare -A TUNNEL_IPS=(
|
||||
["ml110"]="192.168.11.10"
|
||||
["r630-01"]="192.168.11.11"
|
||||
["r630-02"]="192.168.11.12"
|
||||
["r630-03"]="192.168.11.13"
|
||||
["r630-04"]="192.168.11.14"
|
||||
)
|
||||
|
||||
# Check if running on Proxmox host
|
||||
if command -v pct &> /dev/null; then
|
||||
RUN_LOCAL=true
|
||||
else
|
||||
RUN_LOCAL=false
|
||||
fi
|
||||
|
||||
exec_in_container() {
|
||||
local cmd="$1"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct exec "$VMID" -- bash -c "$cmd"
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "pct exec $VMID -- bash -c '$cmd'"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check service status
|
||||
check_service() {
|
||||
local tunnel="$1"
|
||||
local service="cloudflared-${tunnel}"
|
||||
|
||||
if exec_in_container "systemctl is-active --quiet $service 2>/dev/null"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check service logs for errors
|
||||
check_logs() {
|
||||
local tunnel="$1"
|
||||
local service="cloudflared-${tunnel}"
|
||||
|
||||
# Check for recent errors in logs
|
||||
if exec_in_container "journalctl -u $service --since '5 minutes ago' --no-pager | grep -i error | head -1"; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
|
||||
# Check DNS resolution
|
||||
check_dns() {
|
||||
local domain="$1"
|
||||
|
||||
if dig +short "$domain" | grep -q .; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check HTTPS connectivity
|
||||
check_https() {
|
||||
local domain="$1"
|
||||
local http_code
|
||||
|
||||
http_code=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 "https://${domain}" 2>/dev/null || echo "000")
|
||||
|
||||
if [[ "$http_code" =~ ^(200|302|403|401)$ ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check internal connectivity
|
||||
check_internal() {
|
||||
local ip="$1"
|
||||
local port="${2:-8006}"
|
||||
|
||||
if timeout 5 bash -c "echo > /dev/tcp/${ip}/${port}" 2>/dev/null; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Print header
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Cloudflare Tunnel Health Check"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Check each tunnel
|
||||
for tunnel in "${TUNNELS[@]}"; do
|
||||
domain="${TUNNEL_DOMAINS[$tunnel]}"
|
||||
ip="${TUNNEL_IPS[$tunnel]}"
|
||||
service="cloudflared-${tunnel}"
|
||||
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Tunnel: $tunnel ($domain)"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
# Check service status
|
||||
if check_service "$tunnel"; then
|
||||
log_success "Service is running"
|
||||
else
|
||||
log_error "Service is NOT running"
|
||||
echo ""
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check logs
|
||||
if check_logs "$tunnel"; then
|
||||
log_success "No recent errors in logs"
|
||||
else
|
||||
log_warn "Recent errors found in logs"
|
||||
exec_in_container "journalctl -u $service --since '5 minutes ago' --no-pager | grep -i error | head -3"
|
||||
fi
|
||||
|
||||
# Check DNS
|
||||
if check_dns "$domain"; then
|
||||
log_success "DNS resolution: OK"
|
||||
dig +short "$domain" | head -1 | sed 's/^/ → /'
|
||||
else
|
||||
log_error "DNS resolution: FAILED"
|
||||
fi
|
||||
|
||||
# Check HTTPS connectivity
|
||||
if check_https "$domain"; then
|
||||
log_success "HTTPS connectivity: OK"
|
||||
else
|
||||
log_error "HTTPS connectivity: FAILED"
|
||||
fi
|
||||
|
||||
# Check internal connectivity
|
||||
if check_internal "$ip" 8006; then
|
||||
log_success "Internal connectivity to $ip:8006: OK"
|
||||
else
|
||||
log_error "Internal connectivity to $ip:8006: FAILED"
|
||||
fi
|
||||
|
||||
# Get service uptime
|
||||
uptime=$(exec_in_container "systemctl show $service --property=ActiveEnterTimestamp --value 2>/dev/null" || echo "unknown")
|
||||
if [ "$uptime" != "unknown" ]; then
|
||||
log_info "Service started: $uptime"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
# Summary
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
echo "Summary"
|
||||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||||
|
||||
all_healthy=true
|
||||
for tunnel in "${TUNNELS[@]}"; do
|
||||
if ! check_service "$tunnel"; then
|
||||
all_healthy=false
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [ "$all_healthy" = true ]; then
|
||||
log_success "All tunnels are healthy"
|
||||
else
|
||||
log_error "Some tunnels are not healthy"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
107
scripts/cloudflare-tunnels/scripts/complete-automated-setup.sh
Executable file
107
scripts/cloudflare-tunnels/scripts/complete-automated-setup.sh
Executable file
@@ -0,0 +1,107 @@
|
||||
#!/usr/bin/env bash
|
||||
# Complete automated setup - runs all automation steps
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
echo ""
|
||||
log_info "=========================================="
|
||||
log_info " Complete Automated Cloudflare Setup"
|
||||
log_info "=========================================="
|
||||
echo ""
|
||||
|
||||
# Step 1: Verify prerequisites
|
||||
log_info "Step 1: Verifying prerequisites..."
|
||||
if ! "$SCRIPT_DIR/verify-prerequisites.sh"; then
|
||||
log_error "Prerequisites check failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Run Cloudflare API automation
|
||||
log_info ""
|
||||
log_info "Step 2: Running Cloudflare API automation..."
|
||||
log_info "This will create tunnels, DNS records, and Access applications"
|
||||
echo ""
|
||||
|
||||
if ! "$SCRIPT_DIR/automate-cloudflare-setup.sh"; then
|
||||
log_error "Cloudflare API automation failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 3: Parse output and save credentials
|
||||
log_info ""
|
||||
log_info "Step 3: Saving tunnel credentials..."
|
||||
log_warn "Note: You'll need to manually extract tunnel IDs and tokens from the output above"
|
||||
log_warn "Then run: ./scripts/save-tunnel-credentials.sh <name> <id> <token>"
|
||||
echo ""
|
||||
|
||||
# Step 4: Install systemd services
|
||||
log_info ""
|
||||
log_info "Step 4: Installing systemd services..."
|
||||
if ! "$SCRIPT_DIR/setup-multi-tunnel.sh" --skip-credentials; then
|
||||
log_warn "Service installation may need manual completion"
|
||||
fi
|
||||
|
||||
# Step 5: Start services
|
||||
log_info ""
|
||||
log_info "Step 5: Starting tunnel services..."
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
|
||||
if command -v pct &> /dev/null; then
|
||||
for tunnel in ml110 r630-01 r630-02; do
|
||||
if pct exec "$VMID" -- systemctl start "cloudflared-${tunnel}.service" 2>/dev/null; then
|
||||
log_success "Started cloudflared-${tunnel}.service"
|
||||
else
|
||||
log_warn "Could not start cloudflared-${tunnel}.service (may need credentials first)"
|
||||
fi
|
||||
done
|
||||
else
|
||||
log_warn "Cannot start services remotely. Run manually:"
|
||||
echo " ssh root@${PROXMOX_HOST} 'pct exec $VMID -- systemctl start cloudflared-*'"
|
||||
fi
|
||||
|
||||
# Step 6: Health check
|
||||
log_info ""
|
||||
log_info "Step 6: Running health check..."
|
||||
if "$SCRIPT_DIR/check-tunnel-health.sh"; then
|
||||
log_success "Health check completed"
|
||||
else
|
||||
log_warn "Health check found issues (may be expected if credentials not saved yet)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_success "=========================================="
|
||||
log_success " Automated Setup Complete"
|
||||
log_success "=========================================="
|
||||
echo ""
|
||||
log_info "Next steps:"
|
||||
echo ""
|
||||
echo "1. Extract tunnel IDs and tokens from Step 2 output"
|
||||
echo "2. Save credentials:"
|
||||
echo " ./scripts/save-tunnel-credentials.sh ml110 <id> <token>"
|
||||
echo " ./scripts/save-tunnel-credentials.sh r630-01 <id> <token>"
|
||||
echo " ./scripts/save-tunnel-credentials.sh r630-02 <id> <token>"
|
||||
echo ""
|
||||
echo "3. Start services:"
|
||||
echo " systemctl start cloudflared-*"
|
||||
echo ""
|
||||
echo "4. Verify:"
|
||||
echo " ./scripts/check-tunnel-health.sh"
|
||||
echo ""
|
||||
|
||||
171
scripts/cloudflare-tunnels/scripts/configure-access-policies.sh
Executable file
171
scripts/cloudflare-tunnels/scripts/configure-access-policies.sh
Executable file
@@ -0,0 +1,171 @@
|
||||
#!/usr/bin/env bash
|
||||
# Configure Cloudflare Access policies with allowed email addresses
|
||||
# Usage: ./configure-access-policies.sh [email1] [email2] [email3] ...
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
# Load .env
|
||||
if [ -f "$TUNNELS_DIR/../../.env" ]; then
|
||||
source "$TUNNELS_DIR/../../.env" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [[ -z "${CLOUDFLARE_ACCOUNT_ID:-}" ]] || [[ -z "${CLOUDFLARE_API_KEY:-}" ]] || [[ -z "${CLOUDFLARE_EMAIL:-}" ]]; then
|
||||
log_error "Cloudflare credentials not found in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get email addresses from command line or prompt
|
||||
ALLOWED_EMAILS=("$@")
|
||||
|
||||
if [ ${#ALLOWED_EMAILS[@]} -eq 0 ]; then
|
||||
log_info "Enter allowed email addresses (one per line, empty line to finish):"
|
||||
while IFS= read -r email; do
|
||||
[[ -z "$email" ]] && break
|
||||
[[ "$email" =~ ^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$ ]] || {
|
||||
log_warn "Invalid email format: $email (skipping)"
|
||||
continue
|
||||
}
|
||||
ALLOWED_EMAILS+=("$email")
|
||||
done
|
||||
fi
|
||||
|
||||
if [ ${#ALLOWED_EMAILS[@]} -eq 0 ]; then
|
||||
log_error "No email addresses provided"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Allowed emails: ${ALLOWED_EMAILS[*]}"
|
||||
echo ""
|
||||
|
||||
# Function to make API request
|
||||
cf_api_request() {
|
||||
local method="$1"
|
||||
local endpoint="$2"
|
||||
local data="${3:-}"
|
||||
|
||||
local url="https://api.cloudflare.com/client/v4${endpoint}"
|
||||
local temp_file=$(mktemp)
|
||||
local http_code
|
||||
|
||||
if [[ -n "$data" ]]; then
|
||||
http_code=$(curl -s -o "$temp_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
|
||||
-H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" 2>/dev/null)
|
||||
else
|
||||
http_code=$(curl -s -o "$temp_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
|
||||
-H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
|
||||
-H "Content-Type: application/json" 2>/dev/null)
|
||||
fi
|
||||
|
||||
local response=$(cat "$temp_file" 2>/dev/null || echo "")
|
||||
rm -f "$temp_file"
|
||||
|
||||
if [[ "$http_code" != "200" ]] && [[ "$http_code" != "201" ]]; then
|
||||
log_error "API request failed (HTTP $http_code)"
|
||||
echo "$response" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "$response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
# Get Access applications
|
||||
log_info "Fetching Access applications..."
|
||||
APPS_RESPONSE=$(cf_api_request "GET" "/accounts/${CLOUDFLARE_ACCOUNT_ID}/access/apps" 2>&1)
|
||||
|
||||
declare -A APP_IDS=()
|
||||
for hostname in ml110-01.d-bis.org r630-01.d-bis.org r630-02.d-bis.org; do
|
||||
app_id=$(echo "$APPS_RESPONSE" | jq -r ".result[]? | select(.domain? == \"${hostname}\") | .id" 2>/dev/null || echo "")
|
||||
if [[ -n "$app_id" ]] && [[ "$app_id" != "null" ]] && [[ "$app_id" != "" ]]; then
|
||||
APP_IDS["$hostname"]="$app_id"
|
||||
log_success "Found app for $hostname: $app_id"
|
||||
else
|
||||
log_warn "No app found for $hostname"
|
||||
fi
|
||||
done
|
||||
|
||||
if [ ${#APP_IDS[@]} -eq 0 ]; then
|
||||
log_error "No Access applications found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# Build email include array
|
||||
EMAIL_INCLUDES=$(printf '%s\n' "${ALLOWED_EMAILS[@]}" | jq -R . | jq -s . | jq 'map({email: {email: .}})')
|
||||
|
||||
# Configure policy for each app
|
||||
for hostname in "${!APP_IDS[@]}"; do
|
||||
app_id="${APP_IDS[$hostname]}"
|
||||
|
||||
log_info "Configuring policy for $hostname..."
|
||||
|
||||
# Get existing policies
|
||||
POLICIES_RESPONSE=$(cf_api_request "GET" "/accounts/${CLOUDFLARE_ACCOUNT_ID}/access/apps/${app_id}/policies" 2>&1)
|
||||
EXISTING_POLICY_ID=$(echo "$POLICIES_RESPONSE" | jq -r '.result[] | select(.name == "Allow Team Access") | .id' 2>/dev/null || echo "")
|
||||
|
||||
# Build policy data
|
||||
POLICY_DATA=$(jq -n \
|
||||
--argjson emails "$EMAIL_INCLUDES" \
|
||||
'{
|
||||
name: "Allow Team Access",
|
||||
decision: "allow",
|
||||
include: $emails,
|
||||
require: [
|
||||
{
|
||||
email: {}
|
||||
}
|
||||
]
|
||||
}')
|
||||
|
||||
if [[ -n "$EXISTING_POLICY_ID" ]] && [[ "$EXISTING_POLICY_ID" != "null" ]]; then
|
||||
# Update existing policy
|
||||
log_info " Updating existing policy..."
|
||||
response=$(cf_api_request "PUT" "/accounts/${CLOUDFLARE_ACCOUNT_ID}/access/apps/${app_id}/policies/${EXISTING_POLICY_ID}" "$POLICY_DATA" 2>&1)
|
||||
else
|
||||
# Create new policy
|
||||
log_info " Creating new policy..."
|
||||
response=$(cf_api_request "POST" "/accounts/${CLOUDFLARE_ACCOUNT_ID}/access/apps/${app_id}/policies" "$POLICY_DATA" 2>&1)
|
||||
fi
|
||||
|
||||
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
||||
log_success " ✓ Policy configured for $hostname"
|
||||
else
|
||||
log_error " ✗ Failed to configure policy for $hostname"
|
||||
echo "$response" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
log_success "=== Access Policies Configured ==="
|
||||
log_info "Allowed emails:"
|
||||
for email in "${ALLOWED_EMAILS[@]}"; do
|
||||
echo " - $email"
|
||||
done
|
||||
echo ""
|
||||
log_info "These emails can now access:"
|
||||
for hostname in "${!APP_IDS[@]}"; do
|
||||
echo " - https://$hostname"
|
||||
done
|
||||
|
||||
184
scripts/cloudflare-tunnels/scripts/configure-r630-02-for-migration.sh
Executable file
184
scripts/cloudflare-tunnels/scripts/configure-r630-02-for-migration.sh
Executable file
@@ -0,0 +1,184 @@
|
||||
#!/usr/bin/env bash
|
||||
# Configure tunnel-r630-02 to be ready for migration/token generation
|
||||
# This ensures the tunnel has proper configuration before getting a token
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
# Load .env
|
||||
if [ -f "$TUNNELS_DIR/../../.env" ]; then
|
||||
source "$TUNNELS_DIR/../../.env" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [[ -z "${CLOUDFLARE_ACCOUNT_ID:-}" ]] || [[ -z "${CLOUDFLARE_API_KEY:-}" ]] || [[ -z "${CLOUDFLARE_EMAIL:-}" ]]; then
|
||||
log_error "Cloudflare credentials not found in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TUNNEL_ID="0876f12b-64d7-4927-9ab3-94cb6cf48af9"
|
||||
HOSTNAME="r630-02.d-bis.org"
|
||||
TARGET="https://192.168.11.12:8006"
|
||||
|
||||
log_info "=== Configuring tunnel-r630-02 for Migration ==="
|
||||
echo ""
|
||||
|
||||
# Function to make API request
|
||||
cf_api_request() {
|
||||
local method="$1"
|
||||
local endpoint="$2"
|
||||
local data="${3:-}"
|
||||
|
||||
local url="https://api.cloudflare.com/client/v4${endpoint}"
|
||||
local temp_file=$(mktemp)
|
||||
local http_code
|
||||
|
||||
if [[ -n "$data" ]]; then
|
||||
http_code=$(curl -s -o "$temp_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
|
||||
-H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" 2>/dev/null)
|
||||
else
|
||||
http_code=$(curl -s -o "$temp_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
|
||||
-H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
|
||||
-H "Content-Type: application/json" 2>/dev/null)
|
||||
fi
|
||||
|
||||
local response=$(cat "$temp_file" 2>/dev/null || echo "")
|
||||
rm -f "$temp_file"
|
||||
|
||||
if [[ "$http_code" != "200" ]] && [[ "$http_code" != "201" ]]; then
|
||||
log_error "API request failed (HTTP $http_code)"
|
||||
echo "$response" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "$response"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
# Step 1: Ensure tunnel configuration exists
|
||||
log_info "Step 1: Configuring tunnel route..."
|
||||
|
||||
CONFIG_DATA=$(jq -n \
|
||||
--arg hostname "$HOSTNAME" \
|
||||
--arg target "$TARGET" \
|
||||
'{
|
||||
config: {
|
||||
ingress: [
|
||||
{
|
||||
hostname: $hostname,
|
||||
service: $target,
|
||||
originRequest: {
|
||||
noHappyEyeballs: true,
|
||||
connectTimeout: "30s",
|
||||
tcpKeepAlive: "30s",
|
||||
keepAliveConnections: 100,
|
||||
keepAliveTimeout: "90s",
|
||||
disableChunkedEncoding: true,
|
||||
noTLSVerify: true
|
||||
}
|
||||
},
|
||||
{
|
||||
service: "http_status:404"
|
||||
}
|
||||
]
|
||||
}
|
||||
}')
|
||||
|
||||
response=$(cf_api_request "PUT" "/accounts/${CLOUDFLARE_ACCOUNT_ID}/cfd_tunnel/${TUNNEL_ID}/configurations" "$CONFIG_DATA" 2>&1)
|
||||
|
||||
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
||||
log_success "Tunnel route configured"
|
||||
else
|
||||
log_error "Failed to configure tunnel route"
|
||||
echo "$response" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Step 2: Create local config file
|
||||
log_info "Step 2: Creating local config file..."
|
||||
|
||||
CONFIG_FILE="$TUNNELS_DIR/configs/tunnel-r630-02.yml"
|
||||
|
||||
cat > "$CONFIG_FILE" <<EOF
|
||||
# Cloudflare Tunnel Configuration for r630-02 Proxmox Host
|
||||
# Tunnel Name: tunnel-r630-02
|
||||
# Domain: r630-02.d-bis.org
|
||||
# Target: 192.168.11.12:8006 (Proxmox UI)
|
||||
|
||||
tunnel: $TUNNEL_ID
|
||||
credentials-file: /etc/cloudflared/credentials-r630-02.json
|
||||
|
||||
ingress:
|
||||
# Proxmox UI - r630-02
|
||||
- hostname: $HOSTNAME
|
||||
service: $TARGET
|
||||
originRequest:
|
||||
noHappyEyeballs: true
|
||||
connectTimeout: 30s
|
||||
tcpKeepAlive: 30s
|
||||
keepAliveConnections: 100
|
||||
keepAliveTimeout: 90s
|
||||
disableChunkedEncoding: true
|
||||
# Allow self-signed certificates (Proxmox uses self-signed)
|
||||
noTLSVerify: true
|
||||
|
||||
# Catch-all (must be last)
|
||||
- service: http_status:404
|
||||
|
||||
# Metrics endpoint (optional, for monitoring)
|
||||
metrics: 127.0.0.1:9093
|
||||
|
||||
# Logging
|
||||
loglevel: info
|
||||
|
||||
# Grace period for shutdown
|
||||
gracePeriod: 30s
|
||||
EOF
|
||||
|
||||
log_success "Config file created: $CONFIG_FILE"
|
||||
|
||||
# Step 3: Check tunnel status
|
||||
log_info "Step 3: Checking tunnel status..."
|
||||
response=$(cf_api_request "GET" "/accounts/${CLOUDFLARE_ACCOUNT_ID}/cfd_tunnel/${TUNNEL_ID}" 2>&1)
|
||||
|
||||
if echo "$response" | jq -e '.result' >/dev/null 2>&1; then
|
||||
status=$(echo "$response" | jq -r '.result.status // "unknown"')
|
||||
remote_config=$(echo "$response" | jq -r '.result.remote_config // false')
|
||||
|
||||
log_info "Tunnel status: $status"
|
||||
log_info "Remote config: $remote_config"
|
||||
|
||||
if [[ "$status" == "healthy" ]] && [[ "$remote_config" == "true" ]]; then
|
||||
log_success "Tunnel is ready for migration!"
|
||||
else
|
||||
log_warn "Tunnel needs to be connected to become healthy"
|
||||
log_info "Once you install and start the tunnel, it will become healthy"
|
||||
fi
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_success "=== Configuration Complete ==="
|
||||
log_info "Next steps:"
|
||||
log_info "1. Get token from Cloudflare Dashboard:"
|
||||
log_info " https://one.dash.cloudflare.com/ → Zero Trust → Networks → Tunnels → tunnel-r630-02"
|
||||
log_info "2. Install with: sudo cloudflared service install <token>"
|
||||
log_info "3. Or use the install script once you have the token"
|
||||
|
||||
76
scripts/cloudflare-tunnels/scripts/deploy-all.sh
Executable file
76
scripts/cloudflare-tunnels/scripts/deploy-all.sh
Executable file
@@ -0,0 +1,76 @@
|
||||
#!/usr/bin/env bash
|
||||
# Complete deployment script - runs all setup steps
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Cloudflare Multi-Tunnel Deployment"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
# Step 1: Verify prerequisites
|
||||
log_info "Step 1: Verifying prerequisites..."
|
||||
if "$SCRIPT_DIR/verify-prerequisites.sh"; then
|
||||
log_success "Prerequisites verified"
|
||||
else
|
||||
log_error "Prerequisites check failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "Step 2: Running setup script..."
|
||||
log_warn "You will be prompted for tunnel credentials"
|
||||
echo ""
|
||||
|
||||
# Step 2: Run setup
|
||||
if "$SCRIPT_DIR/setup-multi-tunnel.sh"; then
|
||||
log_success "Setup completed"
|
||||
else
|
||||
log_error "Setup failed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_info "Step 3: Running health check..."
|
||||
if "$SCRIPT_DIR/check-tunnel-health.sh"; then
|
||||
log_success "Health check completed"
|
||||
else
|
||||
log_warn "Health check found issues (may be expected if tunnels not fully configured)"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
log_success "=========================================="
|
||||
log_success " Deployment Complete"
|
||||
log_success "=========================================="
|
||||
echo ""
|
||||
log_info "Next steps:"
|
||||
echo ""
|
||||
echo "1. Verify tunnels are running:"
|
||||
echo " systemctl status cloudflared-*"
|
||||
echo ""
|
||||
echo "2. Configure Cloudflare Access:"
|
||||
echo " See: docs/CLOUDFLARE_ACCESS_SETUP.md"
|
||||
echo ""
|
||||
echo "3. Start monitoring:"
|
||||
echo " ./scripts/monitor-tunnels.sh --daemon"
|
||||
echo ""
|
||||
echo "4. Test access:"
|
||||
echo " curl -I https://ml110-01.d-bis.org"
|
||||
echo ""
|
||||
|
||||
153
scripts/cloudflare-tunnels/scripts/generate-credentials.sh
Executable file
153
scripts/cloudflare-tunnels/scripts/generate-credentials.sh
Executable file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env bash
|
||||
# Generate credentials files for existing Cloudflare Tunnels
|
||||
# This script helps create the credentials JSON files needed for existing tunnels
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
# Load account ID from .env if available
|
||||
if [ -f "$TUNNELS_DIR/../../.env" ]; then
|
||||
source "$TUNNELS_DIR/../../.env" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
declare -A TUNNELS=(
|
||||
["ml110"]="tunnel-ml110:ccd7150a-9881-4b8c-a105-9b4ead6e69a2"
|
||||
["r630-01"]="tunnel-r630-01:4481af8f-b24c-4cd3-bdd5-f562f4c97df4"
|
||||
["r630-02"]="tunnel-r630-02:0876f12b-64d7-4927-9ab3-94cb6cf48af9"
|
||||
)
|
||||
|
||||
log_info "=== Cloudflare Tunnel Credentials Generator ==="
|
||||
echo ""
|
||||
log_info "For existing tunnels, you have two options:"
|
||||
echo ""
|
||||
echo "Option 1: Download from Cloudflare Dashboard (Recommended)"
|
||||
echo " 1. Go to: https://one.dash.cloudflare.com/"
|
||||
echo " 2. Navigate to: Zero Trust > Networks > Tunnels"
|
||||
echo " 3. Click on each tunnel"
|
||||
echo " 4. Click 'Configure' > 'Local Management' > 'Download credentials file'"
|
||||
echo " 5. Save as: credentials-<tunnel-name>.json"
|
||||
echo ""
|
||||
echo "Option 2: Use cloudflared CLI (if you have access)"
|
||||
echo " Run: cloudflared tunnel create <tunnel-name>"
|
||||
echo " This will generate credentials for a new tunnel with that name"
|
||||
echo " (Note: This creates a NEW tunnel, not for existing ones)"
|
||||
echo ""
|
||||
read -p "Do you have credentials files ready? (y/n): " has_creds
|
||||
|
||||
if [[ "$has_creds" != "y" && "$has_creds" != "Y" ]]; then
|
||||
log_warn "Please download credentials from Cloudflare Dashboard first"
|
||||
log_info "See: https://developers.cloudflare.com/cloudflare-one/connections/connect-apps/install-and-setup/tunnel-guide/#download-the-credentials-file"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
log_info ""
|
||||
log_info "For each tunnel, provide the path to the credentials JSON file"
|
||||
log_info "Or press Enter to skip and configure manually later"
|
||||
echo ""
|
||||
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
|
||||
# Check if running on Proxmox host
|
||||
if command -v pct &> /dev/null; then
|
||||
RUN_LOCAL=true
|
||||
else
|
||||
RUN_LOCAL=false
|
||||
fi
|
||||
|
||||
exec_in_container() {
|
||||
local cmd="$1"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct exec "$VMID" -- bash -c "$cmd"
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "pct exec $VMID -- bash -c '$cmd'"
|
||||
fi
|
||||
}
|
||||
|
||||
copy_to_container() {
|
||||
local src="$1"
|
||||
local dst="$2"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$src" "$dst"
|
||||
else
|
||||
scp "$src" "root@${PROXMOX_HOST}:/tmp/$(basename "$src")"
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/$(basename "$src") $dst"
|
||||
fi
|
||||
}
|
||||
|
||||
for tunnel_key in "${!TUNNELS[@]}"; do
|
||||
IFS=':' read -r tunnel_name tunnel_id <<< "${TUNNELS[$tunnel_key]}"
|
||||
|
||||
echo ""
|
||||
log_info "=== Tunnel: $tunnel_name (ID: $tunnel_id) ==="
|
||||
read -p "Path to credentials JSON file (or Enter to skip): " creds_path
|
||||
|
||||
if [[ -z "$creds_path" ]]; then
|
||||
log_warn "Skipping $tunnel_name"
|
||||
continue
|
||||
fi
|
||||
|
||||
if [[ ! -f "$creds_path" ]]; then
|
||||
log_error "File not found: $creds_path"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Validate JSON structure
|
||||
if ! jq -e '.AccountTag, .TunnelSecret, .TunnelID' "$creds_path" >/dev/null 2>&1; then
|
||||
log_error "Invalid credentials file format"
|
||||
log_info "Expected format:"
|
||||
log_info ' {'
|
||||
log_info ' "AccountTag": "...",'
|
||||
log_info ' "TunnelSecret": "...",'
|
||||
log_info ' "TunnelID": "...",'
|
||||
log_info ' "TunnelName": "..."'
|
||||
log_info ' }'
|
||||
continue
|
||||
fi
|
||||
|
||||
# Copy to container
|
||||
log_info "Copying credentials to VMID $VMID..."
|
||||
copy_to_container "$creds_path" "/etc/cloudflared/credentials-${tunnel_key}.json"
|
||||
|
||||
# Set permissions
|
||||
exec_in_container "chmod 600 /etc/cloudflared/credentials-${tunnel_key}.json"
|
||||
|
||||
# Update config file to use correct credentials path
|
||||
config_file="$TUNNELS_DIR/configs/tunnel-${tunnel_key}.yml"
|
||||
if [ -f "$config_file" ]; then
|
||||
# Update tunnel ID in config
|
||||
temp_config=$(mktemp)
|
||||
sed "s/<TUNNEL_ID_${tunnel_key^^}>/$tunnel_id/g" "$config_file" > "$temp_config"
|
||||
sed -i "s|credentials-file:.*|credentials-file: /etc/cloudflared/credentials-${tunnel_key}.json|g" "$temp_config"
|
||||
|
||||
# Copy config to container
|
||||
copy_to_container "$temp_config" "/etc/cloudflared/tunnel-${tunnel_key}.yml"
|
||||
rm -f "$temp_config"
|
||||
|
||||
log_success "Credentials and config saved for $tunnel_name"
|
||||
else
|
||||
log_warn "Config file not found: $config_file"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
log_success "=== Credentials Setup Complete ==="
|
||||
log_info "Next steps:"
|
||||
log_info "1. Verify configs: ssh root@${PROXMOX_HOST} 'pct exec $VMID -- ls -la /etc/cloudflared/'"
|
||||
log_info "2. Start services: ssh root@${PROXMOX_HOST} 'pct exec $VMID -- systemctl start cloudflared-*'"
|
||||
log_info "3. Check status: ssh root@${PROXMOX_HOST} 'pct exec $VMID -- systemctl status cloudflared-*'"
|
||||
|
||||
158
scripts/cloudflare-tunnels/scripts/install-all-tunnels.sh
Executable file
158
scripts/cloudflare-tunnels/scripts/install-all-tunnels.sh
Executable file
@@ -0,0 +1,158 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install all 3 Cloudflare tunnels using tokens
|
||||
# Usage: ./install-all-tunnels.sh <ml110-token> <r630-01-token> <r630-02-token>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.12}"
|
||||
VMID="${VMID:-102}"
|
||||
|
||||
declare -A TUNNELS=(
|
||||
["ml110"]="tunnel-ml110:ccd7150a-9881-4b8c-a105-9b4ead6e69a2:ml110-01.d-bis.org:https://192.168.11.10:8006"
|
||||
["r630-01"]="tunnel-r630-01:4481af8f-b24c-4cd3-bdd5-f562f4c97df4:r630-01.d-bis.org:https://192.168.11.11:8006"
|
||||
["r630-02"]="tunnel-r630-02:0876f12b-64d7-4927-9ab3-94cb6cf48af9:r630-02.d-bis.org:https://192.168.11.12:8006"
|
||||
)
|
||||
|
||||
# Check if tokens provided
|
||||
if [ $# -lt 3 ]; then
|
||||
log_error "Usage: $0 <ml110-token> <r630-01-token> <r630-02-token>"
|
||||
echo ""
|
||||
log_info "Example:"
|
||||
echo " $0 \\"
|
||||
echo " 'eyJhIjoi...' \\"
|
||||
echo " 'eyJhIjoi...' \\"
|
||||
echo " 'eyJhIjoi...'"
|
||||
echo ""
|
||||
log_info "Get tokens from: https://one.dash.cloudflare.com/ → Zero Trust → Networks → Tunnels"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TOKEN_ML110="$1"
|
||||
TOKEN_R630_01="$2"
|
||||
TOKEN_R630_02="$3"
|
||||
|
||||
declare -A TOKENS=(
|
||||
["ml110"]="$TOKEN_ML110"
|
||||
["r630-01"]="$TOKEN_R630_01"
|
||||
["r630-02"]="$TOKEN_R630_02"
|
||||
)
|
||||
|
||||
log_info "=== Installing All Cloudflare Tunnels ==="
|
||||
echo ""
|
||||
|
||||
# Function to install a tunnel
|
||||
install_tunnel() {
|
||||
local tunnel_key="$1"
|
||||
local token="$2"
|
||||
IFS=':' read -r tunnel_name tunnel_id hostname target <<< "${TUNNELS[$tunnel_key]}"
|
||||
|
||||
log_info "Installing $tunnel_name..."
|
||||
|
||||
# Decode token
|
||||
if ! TOKEN_DATA=$(echo "$token" | base64 -d 2>/dev/null); then
|
||||
log_error "Invalid token format for $tunnel_key"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local account_tag=$(echo "$TOKEN_DATA" | jq -r '.a')
|
||||
local tunnel_secret=$(echo "$TOKEN_DATA" | jq -r '.s')
|
||||
local token_tunnel_id=$(echo "$TOKEN_DATA" | jq -r '.t')
|
||||
|
||||
if [[ "$token_tunnel_id" != "$tunnel_id" ]]; then
|
||||
log_warn "Token tunnel ID ($token_tunnel_id) doesn't match expected ($tunnel_id)"
|
||||
fi
|
||||
|
||||
# Create credentials JSON
|
||||
local creds_json=$(jq -n \
|
||||
--arg account "$account_tag" \
|
||||
--arg secret "$tunnel_secret" \
|
||||
--arg id "$tunnel_id" \
|
||||
--arg name "$tunnel_name" \
|
||||
'{
|
||||
AccountTag: $account,
|
||||
TunnelSecret: $secret,
|
||||
TunnelID: $id,
|
||||
TunnelName: $name
|
||||
}')
|
||||
|
||||
# Create config YAML
|
||||
local config_yml="tunnel: $tunnel_id
|
||||
credentials-file: /etc/cloudflared/credentials-${tunnel_key}.json
|
||||
|
||||
ingress:
|
||||
- hostname: $hostname
|
||||
service: $target
|
||||
originRequest:
|
||||
noHappyEyeballs: true
|
||||
connectTimeout: 30s
|
||||
tcpKeepAlive: 30s
|
||||
keepAliveConnections: 100
|
||||
keepAliveTimeout: 90s
|
||||
disableChunkedEncoding: true
|
||||
noTLSVerify: true
|
||||
- service: http_status:404"
|
||||
|
||||
# Copy files to Proxmox host
|
||||
echo "$creds_json" > /tmp/creds-${tunnel_key}.json
|
||||
echo "$config_yml" > /tmp/config-${tunnel_key}.yml
|
||||
|
||||
scp /tmp/creds-${tunnel_key}.json /tmp/config-${tunnel_key}.yml root@${PROXMOX_HOST}:/tmp/ >/dev/null 2>&1
|
||||
|
||||
# Push to container
|
||||
ssh root@${PROXMOX_HOST} "pct push $VMID /tmp/creds-${tunnel_key}.json /etc/cloudflared/credentials-${tunnel_key}.json && \
|
||||
pct push $VMID /tmp/config-${tunnel_key}.yml /etc/cloudflared/tunnel-${tunnel_key}.yml && \
|
||||
pct exec $VMID -- chmod 600 /etc/cloudflared/credentials-${tunnel_key}.json && \
|
||||
pct exec $VMID -- mkdir -p /etc/cloudflared" 2>&1 | grep -v "failed to create file" || true
|
||||
|
||||
# Install service file
|
||||
local service_file="$TUNNELS_DIR/systemd/cloudflared-${tunnel_key}.service"
|
||||
if [ -f "$service_file" ]; then
|
||||
scp "$service_file" root@${PROXMOX_HOST}:/tmp/ >/dev/null 2>&1
|
||||
ssh root@${PROXMOX_HOST} "pct push $VMID /tmp/cloudflared-${tunnel_key}.service /etc/systemd/system/cloudflared-${tunnel_key}.service && \
|
||||
pct exec $VMID -- systemctl daemon-reload && \
|
||||
pct exec $VMID -- systemctl enable cloudflared-${tunnel_key}.service" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/creds-${tunnel_key}.json /tmp/config-${tunnel_key}.yml
|
||||
|
||||
log_success "✓ $tunnel_name installed"
|
||||
}
|
||||
|
||||
# Install all tunnels
|
||||
for tunnel_key in "${!TUNNELS[@]}"; do
|
||||
install_tunnel "$tunnel_key" "${TOKENS[$tunnel_key]}"
|
||||
echo ""
|
||||
done
|
||||
|
||||
log_success "=== All Tunnels Installed ==="
|
||||
echo ""
|
||||
log_info "Starting services..."
|
||||
ssh root@${PROXMOX_HOST} "pct exec $VMID -- systemctl start cloudflared-ml110 cloudflared-r630-01 cloudflared-r630-02" 2>&1 | grep -v "Unit.*not found" || true
|
||||
|
||||
echo ""
|
||||
log_info "Checking service status..."
|
||||
ssh root@${PROXMOX_HOST} "pct exec $VMID -- systemctl status cloudflared-* --no-pager | grep -E '(Active|tunnel-|●)' | head -10"
|
||||
|
||||
echo ""
|
||||
log_success "Installation complete!"
|
||||
log_info "Test URLs:"
|
||||
log_info " - https://ml110-01.d-bis.org"
|
||||
log_info " - https://r630-01.d-bis.org"
|
||||
log_info " - https://r630-02.d-bis.org"
|
||||
|
||||
129
scripts/cloudflare-tunnels/scripts/install-tunnel.sh
Executable file
129
scripts/cloudflare-tunnels/scripts/install-tunnel.sh
Executable file
@@ -0,0 +1,129 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install and configure a single Cloudflare tunnel
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# Usage
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <tunnel-name> [tunnel-id] [credentials-file]"
|
||||
echo ""
|
||||
echo "Tunnel names: ml110, r630-01, r630-02"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " $0 ml110"
|
||||
echo " $0 ml110 abc123def456 /path/to/credentials.json"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TUNNEL_NAME="$1"
|
||||
TUNNEL_ID="${2:-}"
|
||||
CREDS_FILE="${3:-}"
|
||||
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
|
||||
# Validate tunnel name
|
||||
if [[ ! "$TUNNEL_NAME" =~ ^(ml110|r630-01|r630-02)$ ]]; then
|
||||
log_error "Invalid tunnel name: $TUNNEL_NAME"
|
||||
log_error "Valid names: ml110, r630-01, r630-02"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if running on Proxmox host
|
||||
if command -v pct &> /dev/null; then
|
||||
RUN_LOCAL=true
|
||||
else
|
||||
RUN_LOCAL=false
|
||||
fi
|
||||
|
||||
exec_in_container() {
|
||||
local cmd="$1"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct exec "$VMID" -- bash -c "$cmd"
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "pct exec $VMID -- bash -c '$cmd'"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "Installing tunnel: $TUNNEL_NAME"
|
||||
|
||||
# Check VMID
|
||||
if ! exec_in_container "true"; then
|
||||
log_error "Cannot access VMID $VMID"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy config file
|
||||
config_file="$TUNNELS_DIR/configs/tunnel-${TUNNEL_NAME}.yml"
|
||||
if [ ! -f "$config_file" ]; then
|
||||
log_error "Configuration file not found: $config_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Copying configuration file..."
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$config_file" "/etc/cloudflared/tunnel-${TUNNEL_NAME}.yml"
|
||||
else
|
||||
scp "$config_file" "root@${PROXMOX_HOST}:/tmp/tunnel-${TUNNEL_NAME}.yml"
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/tunnel-${TUNNEL_NAME}.yml /etc/cloudflared/tunnel-${TUNNEL_NAME}.yml"
|
||||
fi
|
||||
|
||||
# Update tunnel ID if provided
|
||||
if [ -n "$TUNNEL_ID" ]; then
|
||||
log_info "Updating tunnel ID..."
|
||||
exec_in_container "sed -i 's/<TUNNEL_ID_${TUNNEL_NAME^^}>/$TUNNEL_ID/g' /etc/cloudflared/tunnel-${TUNNEL_NAME}.yml"
|
||||
log_success "Tunnel ID updated"
|
||||
fi
|
||||
|
||||
# Copy credentials if provided
|
||||
if [ -n "$CREDS_FILE" ] && [ -f "$CREDS_FILE" ]; then
|
||||
log_info "Copying credentials file..."
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$CREDS_FILE" "/etc/cloudflared/tunnel-${TUNNEL_NAME}.json"
|
||||
else
|
||||
scp "$CREDS_FILE" "root@${PROXMOX_HOST}:/tmp/tunnel-${TUNNEL_NAME}.json"
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/tunnel-${TUNNEL_NAME}.json /etc/cloudflared/tunnel-${TUNNEL_NAME}.json"
|
||||
fi
|
||||
exec_in_container "chmod 600 /etc/cloudflared/tunnel-${TUNNEL_NAME}.json"
|
||||
log_success "Credentials file copied"
|
||||
fi
|
||||
|
||||
# Install systemd service
|
||||
service_file="$TUNNELS_DIR/systemd/cloudflared-${TUNNEL_NAME}.service"
|
||||
if [ ! -f "$service_file" ]; then
|
||||
log_error "Service file not found: $service_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Installing systemd service..."
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$service_file" "/tmp/cloudflared-${TUNNEL_NAME}.service"
|
||||
exec_in_container "mv /tmp/cloudflared-${TUNNEL_NAME}.service /etc/systemd/system/cloudflared-${TUNNEL_NAME}.service"
|
||||
else
|
||||
scp "$service_file" "root@${PROXMOX_HOST}:/tmp/cloudflared-${TUNNEL_NAME}.service"
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/cloudflared-${TUNNEL_NAME}.service /etc/systemd/system/cloudflared-${TUNNEL_NAME}.service"
|
||||
exec_in_container "mv /tmp/cloudflared-${TUNNEL_NAME}.service /etc/systemd/system/cloudflared-${TUNNEL_NAME}.service"
|
||||
fi
|
||||
|
||||
# Reload systemd
|
||||
exec_in_container "systemctl daemon-reload"
|
||||
exec_in_container "systemctl enable cloudflared-${TUNNEL_NAME}.service"
|
||||
|
||||
log_success "Tunnel $TUNNEL_NAME installed and enabled"
|
||||
log_info "Start with: systemctl start cloudflared-${TUNNEL_NAME}.service"
|
||||
|
||||
252
scripts/cloudflare-tunnels/scripts/install-with-tokens.sh
Executable file
252
scripts/cloudflare-tunnels/scripts/install-with-tokens.sh
Executable file
@@ -0,0 +1,252 @@
|
||||
#!/usr/bin/env bash
|
||||
# Install cloudflared services using tokens
|
||||
# This is an alternative to credentials files
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
|
||||
declare -A TUNNELS=(
|
||||
["ml110"]="tunnel-ml110:ccd7150a-9881-4b8c-a105-9b4ead6e69a2"
|
||||
["r630-01"]="tunnel-r630-01:4481af8f-b24c-4cd3-bdd5-f562f4c97df4"
|
||||
["r630-02"]="tunnel-r630-02:0876f12b-64d7-4927-9ab3-94cb6cf48af9"
|
||||
["r630-03"]="tunnel-r630-03:<TUNNEL_ID_R630_03>"
|
||||
["r630-04"]="tunnel-r630-04:<TUNNEL_ID_R630_04>"
|
||||
)
|
||||
|
||||
log_info "=== Cloudflare Tunnel Service Installation ==="
|
||||
echo ""
|
||||
log_info "This script will install cloudflared services using tokens"
|
||||
log_info "You need to provide tokens for each tunnel"
|
||||
echo ""
|
||||
|
||||
# Check if running on Proxmox host
|
||||
if command -v pct &> /dev/null; then
|
||||
RUN_LOCAL=true
|
||||
else
|
||||
RUN_LOCAL=false
|
||||
fi
|
||||
|
||||
exec_in_container() {
|
||||
local cmd="$1"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct exec "$VMID" -- bash -c "$cmd"
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "pct exec $VMID -- bash -c '$cmd'"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check if cloudflared is installed
|
||||
log_info "Checking cloudflared installation..."
|
||||
if ! exec_in_container "command -v cloudflared >/dev/null 2>&1"; then
|
||||
log_error "cloudflared is not installed in VMID $VMID"
|
||||
log_info "Install it first:"
|
||||
log_info " ssh root@${PROXMOX_HOST} 'pct exec $VMID -- wget https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb'"
|
||||
log_info " ssh root@${PROXMOX_HOST} 'pct exec $VMID -- dpkg -i cloudflared-linux-amd64.deb'"
|
||||
exit 1
|
||||
fi
|
||||
log_success "cloudflared is installed"
|
||||
|
||||
echo ""
|
||||
log_info "For each tunnel, you can either:"
|
||||
log_info " 1. Provide a token (base64-encoded)"
|
||||
log_info " 2. Press Enter to skip and configure manually later"
|
||||
echo ""
|
||||
|
||||
for tunnel_key in "${!TUNNELS[@]}"; do
|
||||
IFS=':' read -r tunnel_name tunnel_id <<< "${TUNNELS[$tunnel_key]}"
|
||||
|
||||
echo ""
|
||||
log_info "=== Tunnel: $tunnel_name (ID: $tunnel_id) ==="
|
||||
read -p "Enter token (or press Enter to skip): " token
|
||||
|
||||
if [[ -z "$token" ]]; then
|
||||
log_warn "Skipping $tunnel_name"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Validate token format (basic check - should be base64)
|
||||
if ! echo "$token" | base64 -d >/dev/null 2>&1; then
|
||||
log_error "Invalid token format (not base64)"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Decode token to verify
|
||||
token_data=$(echo "$token" | base64 -d 2>/dev/null || echo "")
|
||||
if ! echo "$token_data" | jq -e '.a, .t' >/dev/null 2>&1; then
|
||||
log_error "Invalid token format (not valid JSON)"
|
||||
continue
|
||||
fi
|
||||
|
||||
token_tunnel_id=$(echo "$token_data" | jq -r '.t' 2>/dev/null || echo "")
|
||||
if [[ "$token_tunnel_id" != "$tunnel_id" ]]; then
|
||||
log_warn "Token tunnel ID ($token_tunnel_id) doesn't match expected ($tunnel_id)"
|
||||
read -p "Continue anyway? (y/N): " confirm
|
||||
if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
|
||||
continue
|
||||
fi
|
||||
fi
|
||||
|
||||
log_info "Installing service for $tunnel_name..."
|
||||
|
||||
# Create config file with token
|
||||
temp_config=$(mktemp)
|
||||
cat > "$temp_config" <<EOF
|
||||
tunnel: $tunnel_id
|
||||
credentials-file: /etc/cloudflared/credentials-${tunnel_key}.json
|
||||
|
||||
ingress:
|
||||
EOF
|
||||
|
||||
# Add ingress rules based on tunnel
|
||||
case "$tunnel_key" in
|
||||
"ml110")
|
||||
cat >> "$temp_config" <<'INGRESS'
|
||||
- hostname: ml110-01.d-bis.org
|
||||
service: https://192.168.11.10:8006
|
||||
originRequest:
|
||||
noHappyEyeballs: true
|
||||
connectTimeout: 30s
|
||||
tcpKeepAlive: 30s
|
||||
keepAliveConnections: 100
|
||||
keepAliveTimeout: 90s
|
||||
disableChunkedEncoding: true
|
||||
noTLSVerify: true
|
||||
INGRESS
|
||||
;;
|
||||
"r630-01")
|
||||
cat >> "$temp_config" <<'INGRESS'
|
||||
- hostname: r630-01.d-bis.org
|
||||
service: https://192.168.11.11:8006
|
||||
originRequest:
|
||||
noHappyEyeballs: true
|
||||
connectTimeout: 30s
|
||||
tcpKeepAlive: 30s
|
||||
keepAliveConnections: 100
|
||||
keepAliveTimeout: 90s
|
||||
disableChunkedEncoding: true
|
||||
noTLSVerify: true
|
||||
INGRESS
|
||||
;;
|
||||
"r630-02")
|
||||
cat >> "$temp_config" <<'INGRESS'
|
||||
- hostname: r630-02.d-bis.org
|
||||
service: https://192.168.11.12:8006
|
||||
originRequest:
|
||||
noHappyEyeballs: true
|
||||
connectTimeout: 30s
|
||||
tcpKeepAlive: 30s
|
||||
keepAliveConnections: 100
|
||||
keepAliveTimeout: 90s
|
||||
disableChunkedEncoding: true
|
||||
noTLSVerify: true
|
||||
INGRESS
|
||||
;;
|
||||
"r630-03")
|
||||
cat >> "$temp_config" <<'INGRESS'
|
||||
- hostname: r630-03.d-bis.org
|
||||
service: https://192.168.11.13:8006
|
||||
originRequest:
|
||||
noHappyEyeballs: true
|
||||
connectTimeout: 30s
|
||||
tcpKeepAlive: 30s
|
||||
keepAliveConnections: 100
|
||||
keepAliveTimeout: 90s
|
||||
disableChunkedEncoding: true
|
||||
noTLSVerify: true
|
||||
INGRESS
|
||||
;;
|
||||
"r630-04")
|
||||
cat >> "$temp_config" <<'INGRESS'
|
||||
- hostname: r630-04.d-bis.org
|
||||
service: https://192.168.11.14:8006
|
||||
originRequest:
|
||||
noHappyEyeballs: true
|
||||
connectTimeout: 30s
|
||||
tcpKeepAlive: 30s
|
||||
keepAliveConnections: 100
|
||||
keepAliveTimeout: 90s
|
||||
disableChunkedEncoding: true
|
||||
noTLSVerify: true
|
||||
INGRESS
|
||||
;;
|
||||
esac
|
||||
|
||||
cat >> "$temp_config" <<EOF
|
||||
- service: http_status:404
|
||||
EOF
|
||||
|
||||
# Copy config to container
|
||||
log_info " Copying config to VMID $VMID..."
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$temp_config" "/etc/cloudflared/tunnel-${tunnel_key}.yml"
|
||||
else
|
||||
scp "$temp_config" "root@${PROXMOX_HOST}:/tmp/tunnel-${tunnel_key}.yml" >/dev/null 2>&1
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/tunnel-${tunnel_key}.yml /etc/cloudflared/tunnel-${tunnel_key}.yml" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Convert token to credentials file format
|
||||
token_json=$(echo "$token" | base64 -d | jq -r '{AccountTag: .a, TunnelSecret: .s, TunnelID: .t, TunnelName: "'"$tunnel_name"'"}')
|
||||
|
||||
temp_creds=$(mktemp)
|
||||
echo "$token_json" > "$temp_creds"
|
||||
|
||||
# Copy credentials to container
|
||||
log_info " Copying credentials to VMID $VMID..."
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$temp_creds" "/etc/cloudflared/credentials-${tunnel_key}.json"
|
||||
else
|
||||
scp "$temp_creds" "root@${PROXMOX_HOST}:/tmp/credentials-${tunnel_key}.json" >/dev/null 2>&1
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/credentials-${tunnel_key}.json /etc/cloudflared/credentials-${tunnel_key}.json" >/dev/null 2>&1
|
||||
fi
|
||||
|
||||
# Set permissions
|
||||
exec_in_container "chmod 600 /etc/cloudflared/credentials-${tunnel_key}.json"
|
||||
|
||||
# Update config to use correct credentials path
|
||||
exec_in_container "sed -i 's|credentials-file:.*|credentials-file: /etc/cloudflared/credentials-${tunnel_key}.json|g' /etc/cloudflared/tunnel-${tunnel_key}.yml"
|
||||
|
||||
# Install systemd service
|
||||
log_info " Installing systemd service..."
|
||||
service_file="$TUNNELS_DIR/systemd/cloudflared-${tunnel_key}.service"
|
||||
if [ -f "$service_file" ]; then
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$service_file" "/etc/systemd/system/cloudflared-${tunnel_key}.service"
|
||||
else
|
||||
scp "$service_file" "root@${PROXMOX_HOST}:/tmp/cloudflared-${tunnel_key}.service" >/dev/null 2>&1
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/cloudflared-${tunnel_key}.service /etc/systemd/system/cloudflared-${tunnel_key}.service" >/dev/null 2>&1
|
||||
fi
|
||||
exec_in_container "systemctl daemon-reload"
|
||||
exec_in_container "systemctl enable cloudflared-${tunnel_key}.service"
|
||||
log_success " ✓ Service installed and enabled"
|
||||
else
|
||||
log_warn " Service file not found: $service_file"
|
||||
fi
|
||||
|
||||
rm -f "$temp_config" "$temp_creds"
|
||||
|
||||
log_success "✓ $tunnel_name configured"
|
||||
done
|
||||
|
||||
echo ""
|
||||
log_success "=== Installation Complete ==="
|
||||
log_info "Next steps:"
|
||||
log_info "1. Start services: ssh root@${PROXMOX_HOST} 'pct exec $VMID -- systemctl start cloudflared-*'"
|
||||
log_info "2. Check status: ssh root@${PROXMOX_HOST} 'pct exec $VMID -- systemctl status cloudflared-*'"
|
||||
|
||||
164
scripts/cloudflare-tunnels/scripts/monitor-tunnels.sh
Executable file
164
scripts/cloudflare-tunnels/scripts/monitor-tunnels.sh
Executable file
@@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
# Continuous health monitoring for Cloudflare tunnels
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# Configuration
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
TUNNELS=("ml110" "r630-01" "r630-02")
|
||||
CHECK_INTERVAL="${CHECK_INTERVAL:-60}" # seconds
|
||||
LOG_FILE="${LOG_FILE:-/var/log/cloudflared-monitor.log}"
|
||||
ALERT_SCRIPT="$SCRIPT_DIR/alert-tunnel-failure.sh"
|
||||
|
||||
# Check if running as daemon
|
||||
DAEMON=false
|
||||
if [[ "${1:-}" == "--daemon" ]]; then
|
||||
DAEMON=true
|
||||
fi
|
||||
|
||||
# Check if running on Proxmox host
|
||||
if command -v pct &> /dev/null; then
|
||||
RUN_LOCAL=true
|
||||
else
|
||||
RUN_LOCAL=false
|
||||
fi
|
||||
|
||||
exec_in_container() {
|
||||
local cmd="$1"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct exec "$VMID" -- bash -c "$cmd"
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "pct exec $VMID -- bash -c '$cmd'"
|
||||
fi
|
||||
}
|
||||
|
||||
# Check tunnel health
|
||||
check_tunnel() {
|
||||
local tunnel="$1"
|
||||
local service="cloudflared-${tunnel}"
|
||||
|
||||
# Check if service is active
|
||||
if exec_in_container "systemctl is-active --quiet $service"; then
|
||||
# Check if service is actually running (not just enabled)
|
||||
if exec_in_container "systemctl is-active $service 2>/dev/null | grep -q active"; then
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Check tunnel connectivity
|
||||
check_connectivity() {
|
||||
local tunnel="$1"
|
||||
local domain=""
|
||||
|
||||
case "$tunnel" in
|
||||
ml110) domain="ml110-01.d-bis.org" ;;
|
||||
r630-01) domain="r630-01.d-bis.org" ;;
|
||||
r630-02) domain="r630-02.d-bis.org" ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
|
||||
# Try to connect via HTTPS (should get Cloudflare Access page or redirect)
|
||||
if curl -s -o /dev/null -w "%{http_code}" --max-time 10 "https://${domain}" | grep -qE "^(200|302|403|401)"; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
# Log message
|
||||
log_message() {
|
||||
local level="$1"
|
||||
local message="$2"
|
||||
local timestamp=$(date '+%Y-%m-%d %H:%M:%S')
|
||||
|
||||
if [ "$DAEMON" = true ]; then
|
||||
echo "[$timestamp] [$level] $message" >> "$LOG_FILE"
|
||||
else
|
||||
case "$level" in
|
||||
INFO) log_info "$message" ;;
|
||||
SUCCESS) log_success "$message" ;;
|
||||
WARN) log_warn "$message" ;;
|
||||
ERROR) log_error "$message" ;;
|
||||
esac
|
||||
fi
|
||||
}
|
||||
|
||||
# Monitor loop
|
||||
monitor_loop() {
|
||||
local failed_tunnels=()
|
||||
|
||||
while true; do
|
||||
for tunnel in "${TUNNELS[@]}"; do
|
||||
# Check service status
|
||||
if ! check_tunnel "$tunnel"; then
|
||||
log_message "ERROR" "Tunnel $tunnel service is not running"
|
||||
|
||||
# Alert if not already alerted
|
||||
if [[ ! " ${failed_tunnels[@]} " =~ " ${tunnel} " ]]; then
|
||||
failed_tunnels+=("$tunnel")
|
||||
if [ -f "$ALERT_SCRIPT" ]; then
|
||||
"$ALERT_SCRIPT" "$tunnel" "service_down"
|
||||
fi
|
||||
|
||||
# Attempt restart
|
||||
log_message "INFO" "Attempting to restart tunnel $tunnel"
|
||||
exec_in_container "systemctl restart cloudflared-${tunnel}.service" || true
|
||||
sleep 5
|
||||
fi
|
||||
else
|
||||
# Service is running, check connectivity
|
||||
if ! check_connectivity "$tunnel"; then
|
||||
log_message "WARN" "Tunnel $tunnel service is running but connectivity check failed"
|
||||
else
|
||||
log_message "SUCCESS" "Tunnel $tunnel is healthy"
|
||||
|
||||
# Remove from failed list if it was there
|
||||
failed_tunnels=("${failed_tunnels[@]/$tunnel}")
|
||||
fi
|
||||
fi
|
||||
done
|
||||
|
||||
# Sleep before next check
|
||||
sleep "$CHECK_INTERVAL"
|
||||
done
|
||||
}
|
||||
|
||||
# Main
|
||||
main() {
|
||||
log_message "INFO" "Starting tunnel monitoring"
|
||||
log_message "INFO" "Monitoring tunnels: ${TUNNELS[*]}"
|
||||
log_message "INFO" "Check interval: ${CHECK_INTERVAL}s"
|
||||
|
||||
if [ "$DAEMON" = true ]; then
|
||||
log_message "INFO" "Running in daemon mode. Logs: $LOG_FILE"
|
||||
# Redirect output to log file
|
||||
monitor_loop >> "$LOG_FILE" 2>&1 &
|
||||
echo $! > /tmp/cloudflared-monitor.pid
|
||||
log_message "INFO" "Monitor started with PID: $(cat /tmp/cloudflared-monitor.pid)"
|
||||
else
|
||||
log_message "INFO" "Running in foreground mode. Press Ctrl+C to stop."
|
||||
monitor_loop
|
||||
fi
|
||||
}
|
||||
|
||||
# Handle signals
|
||||
trap 'log_message "INFO" "Monitor stopped"; exit 0' SIGINT SIGTERM
|
||||
|
||||
main
|
||||
|
||||
127
scripts/cloudflare-tunnels/scripts/quick-install-token.sh
Executable file
127
scripts/cloudflare-tunnels/scripts/quick-install-token.sh
Executable file
@@ -0,0 +1,127 @@
|
||||
#!/usr/bin/env bash
|
||||
# Quick install using a single token (for ml110 tunnel)
|
||||
# Usage: ./quick-install-token.sh <token>
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
TOKEN="${1:-}"
|
||||
|
||||
if [[ -z "$TOKEN" ]]; then
|
||||
echo "Usage: $0 <token>"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " $0 eyJhIjoiNTJhZDU3YTcxNjcxYzVmYzAwOWVkZjA3NDQ2NTgxOTYiLCJ0IjoiY2NkNzE1MGEtOTg4MS00YjhjLWExMDUtOWI0ZWFkNmU2OWEyIiwicyI6IkZtems1ZDdUWDR0OW03Q0huVU9DYTYyTFdiQVFPZkZKa2duRHhQdExkZldKNGVvTHgyckw5K2szVCs5N0lFTFFoNGdHdHZLbzNacGZpNmI4TkdxdUlnPT0ifQ=="
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
|
||||
# Decode token
|
||||
TOKEN_DATA=$(echo "$TOKEN" | base64 -d)
|
||||
TUNNEL_ID=$(echo "$TOKEN_DATA" | jq -r '.t')
|
||||
ACCOUNT_TAG=$(echo "$TOKEN_DATA" | jq -r '.a')
|
||||
TUNNEL_SECRET=$(echo "$TOKEN_DATA" | jq -r '.s')
|
||||
|
||||
echo "Token decoded:"
|
||||
echo " Tunnel ID: $TUNNEL_ID"
|
||||
echo " Account Tag: $ACCOUNT_TAG"
|
||||
echo ""
|
||||
|
||||
# Determine tunnel name based on ID
|
||||
case "$TUNNEL_ID" in
|
||||
"ccd7150a-9881-4b8c-a105-9b4ead6e69a2")
|
||||
TUNNEL_KEY="ml110"
|
||||
TUNNEL_NAME="tunnel-ml110"
|
||||
HOSTNAME="ml110-01.d-bis.org"
|
||||
TARGET="https://192.168.11.10:8006"
|
||||
;;
|
||||
"4481af8f-b24c-4cd3-bdd5-f562f4c97df4")
|
||||
TUNNEL_KEY="r630-01"
|
||||
TUNNEL_NAME="tunnel-r630-01"
|
||||
HOSTNAME="r630-01.d-bis.org"
|
||||
TARGET="https://192.168.11.11:8006"
|
||||
;;
|
||||
"0876f12b-64d7-4927-9ab3-94cb6cf48af9")
|
||||
TUNNEL_KEY="r630-02"
|
||||
TUNNEL_NAME="tunnel-r630-02"
|
||||
HOSTNAME="r630-02.d-bis.org"
|
||||
TARGET="https://192.168.11.12:8006"
|
||||
;;
|
||||
*)
|
||||
echo "Unknown tunnel ID: $TUNNEL_ID"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
|
||||
echo "Installing for: $TUNNEL_NAME"
|
||||
echo " Hostname: $HOSTNAME"
|
||||
echo " Target: $TARGET"
|
||||
echo ""
|
||||
|
||||
# Create credentials file
|
||||
CREDS_JSON=$(jq -n \
|
||||
--arg account "$ACCOUNT_TAG" \
|
||||
--arg secret "$TUNNEL_SECRET" \
|
||||
--arg id "$TUNNEL_ID" \
|
||||
--arg name "$TUNNEL_NAME" \
|
||||
'{
|
||||
AccountTag: $account,
|
||||
TunnelSecret: $secret,
|
||||
TunnelID: $id,
|
||||
TunnelName: $name
|
||||
}')
|
||||
|
||||
# Create config file
|
||||
CONFIG_YML="tunnel: $TUNNEL_ID
|
||||
credentials-file: /etc/cloudflared/credentials-${TUNNEL_KEY}.json
|
||||
|
||||
ingress:
|
||||
- hostname: $HOSTNAME
|
||||
service: $TARGET
|
||||
originRequest:
|
||||
noHappyEyeballs: true
|
||||
connectTimeout: 30s
|
||||
tcpKeepAlive: 30s
|
||||
keepAliveConnections: 100
|
||||
keepAliveTimeout: 90s
|
||||
disableChunkedEncoding: true
|
||||
noTLSVerify: true
|
||||
- service: http_status:404"
|
||||
|
||||
# Copy to container
|
||||
echo "Copying files to VMID $VMID..."
|
||||
|
||||
# Credentials
|
||||
echo "$CREDS_JSON" > /tmp/credentials-${TUNNEL_KEY}.json
|
||||
scp /tmp/credentials-${TUNNEL_KEY}.json root@${PROXMOX_HOST}:/tmp/ >/dev/null 2>&1
|
||||
ssh root@${PROXMOX_HOST} "pct push $VMID /tmp/credentials-${TUNNEL_KEY}.json /etc/cloudflared/credentials-${TUNNEL_KEY}.json" >/dev/null 2>&1
|
||||
ssh root@${PROXMOX_HOST} "pct exec $VMID -- chmod 600 /etc/cloudflared/credentials-${TUNNEL_KEY}.json"
|
||||
|
||||
# Config
|
||||
echo "$CONFIG_YML" > /tmp/tunnel-${TUNNEL_KEY}.yml
|
||||
scp /tmp/tunnel-${TUNNEL_KEY}.yml root@${PROXMOX_HOST}:/tmp/ >/dev/null 2>&1
|
||||
ssh root@${PROXMOX_HOST} "pct push $VMID /tmp/tunnel-${TUNNEL_KEY}.yml /etc/cloudflared/tunnel-${TUNNEL_KEY}.yml" >/dev/null 2>&1
|
||||
|
||||
# Service file
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
SERVICE_FILE="$TUNNELS_DIR/systemd/cloudflared-${TUNNEL_KEY}.service"
|
||||
|
||||
if [ -f "$SERVICE_FILE" ]; then
|
||||
scp "$SERVICE_FILE" root@${PROXMOX_HOST}:/tmp/ >/dev/null 2>&1
|
||||
ssh root@${PROXMOX_HOST} "pct push $VMID /tmp/cloudflared-${TUNNEL_KEY}.service /etc/systemd/system/cloudflared-${TUNNEL_KEY}.service" >/dev/null 2>&1
|
||||
ssh root@${PROXMOX_HOST} "pct exec $VMID -- systemctl daemon-reload"
|
||||
ssh root@${PROXMOX_HOST} "pct exec $VMID -- systemctl enable cloudflared-${TUNNEL_KEY}.service"
|
||||
echo "✓ Service installed and enabled"
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -f /tmp/credentials-${TUNNEL_KEY}.json /tmp/tunnel-${TUNNEL_KEY}.yml
|
||||
|
||||
echo ""
|
||||
echo "✓ Installation complete for $TUNNEL_NAME"
|
||||
echo ""
|
||||
echo "Start service:"
|
||||
echo " ssh root@${PROXMOX_HOST} 'pct exec $VMID -- systemctl start cloudflared-${TUNNEL_KEY}'"
|
||||
|
||||
87
scripts/cloudflare-tunnels/scripts/restart-tunnel.sh
Executable file
87
scripts/cloudflare-tunnels/scripts/restart-tunnel.sh
Executable file
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/env bash
|
||||
# Restart a Cloudflare tunnel service
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# Usage
|
||||
if [ $# -lt 1 ]; then
|
||||
echo "Usage: $0 <tunnel-name>"
|
||||
echo ""
|
||||
echo "Tunnel names: ml110, r630-01, r630-02"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TUNNEL_NAME="$1"
|
||||
|
||||
# Validate tunnel name
|
||||
if [[ ! "$TUNNEL_NAME" =~ ^(ml110|r630-01|r630-02)$ ]]; then
|
||||
log_error "Invalid tunnel name: $TUNNEL_NAME"
|
||||
log_error "Valid names: ml110, r630-01, r630-02"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
SERVICE="cloudflared-${TUNNEL_NAME}"
|
||||
|
||||
# Check if running on Proxmox host
|
||||
if command -v pct &> /dev/null; then
|
||||
RUN_LOCAL=true
|
||||
else
|
||||
RUN_LOCAL=false
|
||||
fi
|
||||
|
||||
exec_in_container() {
|
||||
local cmd="$1"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct exec "$VMID" -- bash -c "$cmd"
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "pct exec $VMID -- bash -c '$cmd'"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "Restarting tunnel: $TUNNEL_NAME"
|
||||
|
||||
# Check if service exists
|
||||
if ! exec_in_container "systemctl list-unit-files | grep -q $SERVICE"; then
|
||||
log_error "Service $SERVICE not found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Restart service
|
||||
log_info "Stopping service..."
|
||||
exec_in_container "systemctl stop $SERVICE" || log_warn "Service was not running"
|
||||
|
||||
sleep 2
|
||||
|
||||
log_info "Starting service..."
|
||||
if exec_in_container "systemctl start $SERVICE"; then
|
||||
log_success "Service started"
|
||||
|
||||
# Wait a moment and check status
|
||||
sleep 3
|
||||
if exec_in_container "systemctl is-active --quiet $SERVICE"; then
|
||||
log_success "Tunnel $TUNNEL_NAME is running"
|
||||
else
|
||||
log_error "Tunnel $TUNNEL_NAME failed to start"
|
||||
log_info "Check logs: journalctl -u $SERVICE -n 50"
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
log_error "Failed to start service"
|
||||
log_info "Check logs: journalctl -u $SERVICE -n 50"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
63
scripts/cloudflare-tunnels/scripts/save-credentials-from-file.sh
Executable file
63
scripts/cloudflare-tunnels/scripts/save-credentials-from-file.sh
Executable file
@@ -0,0 +1,63 @@
|
||||
#!/usr/bin/env bash
|
||||
# Load credentials from tunnel-credentials.json and save them
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
CREDS_FILE="$TUNNELS_DIR/tunnel-credentials.json"
|
||||
|
||||
if [ ! -f "$CREDS_FILE" ]; then
|
||||
log_error "Credentials file not found: $CREDS_FILE"
|
||||
log_info "Run ./scripts/automate-cloudflare-setup.sh first"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq >/dev/null 2>&1; then
|
||||
log_error "jq is required"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Loading credentials from: $CREDS_FILE"
|
||||
|
||||
# Load and save each tunnel's credentials
|
||||
for tunnel_name in ml110 r630-01 r630-02; do
|
||||
# Handle tunnel names with hyphens by using array notation
|
||||
if [[ "$tunnel_name" == "r630-01" ]] || [[ "$tunnel_name" == "r630-02" ]]; then
|
||||
tunnel_id=$(jq -r ".[\"${tunnel_name}\"].id // empty" "$CREDS_FILE")
|
||||
tunnel_token=$(jq -r ".[\"${tunnel_name}\"].token // empty" "$CREDS_FILE")
|
||||
else
|
||||
tunnel_id=$(jq -r ".${tunnel_name}.id // empty" "$CREDS_FILE")
|
||||
tunnel_token=$(jq -r ".${tunnel_name}.token // empty" "$CREDS_FILE")
|
||||
fi
|
||||
|
||||
if [[ -z "$tunnel_id" ]] || [[ -z "$tunnel_token" ]]; then
|
||||
log_warn "Missing credentials for $tunnel_name"
|
||||
continue
|
||||
fi
|
||||
|
||||
log_info "Saving credentials for $tunnel_name..."
|
||||
if "$SCRIPT_DIR/save-tunnel-credentials.sh" "$tunnel_name" "$tunnel_id" "$tunnel_token"; then
|
||||
log_success "Credentials saved for $tunnel_name"
|
||||
else
|
||||
log_error "Failed to save credentials for $tunnel_name"
|
||||
fi
|
||||
echo ""
|
||||
done
|
||||
|
||||
log_success "All credentials saved!"
|
||||
log_info "Next: Start services with: systemctl start cloudflared-*"
|
||||
|
||||
115
scripts/cloudflare-tunnels/scripts/save-tunnel-credentials.sh
Executable file
115
scripts/cloudflare-tunnels/scripts/save-tunnel-credentials.sh
Executable file
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
# Save tunnel credentials and update config files after API automation
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../../.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
# Configuration
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
|
||||
declare -A TUNNELS=(
|
||||
["ml110"]="tunnel-ml110"
|
||||
["r630-01"]="tunnel-r630-01"
|
||||
["r630-02"]="tunnel-r630-02"
|
||||
)
|
||||
|
||||
# Usage
|
||||
if [ $# -lt 2 ]; then
|
||||
echo "Usage: $0 <tunnel-name> <tunnel-id> <tunnel-token>"
|
||||
echo ""
|
||||
echo "Example:"
|
||||
echo " $0 ml110 abc123def456 'eyJhIjoi...'"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TUNNEL_NAME="$1"
|
||||
TUNNEL_ID="$2"
|
||||
TUNNEL_TOKEN="$3"
|
||||
|
||||
if [[ ! "$TUNNEL_NAME" =~ ^(ml110|r630-01|r630-02)$ ]]; then
|
||||
log_error "Invalid tunnel name: $TUNNEL_NAME"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Check if running on Proxmox host
|
||||
if command -v pct &> /dev/null; then
|
||||
RUN_LOCAL=true
|
||||
else
|
||||
RUN_LOCAL=false
|
||||
fi
|
||||
|
||||
exec_in_container() {
|
||||
local cmd="$1"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct exec "$VMID" -- bash -c "$cmd"
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "pct exec $VMID -- bash -c '$cmd'"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "Saving credentials for tunnel: $TUNNEL_NAME"
|
||||
|
||||
# Create credentials JSON
|
||||
temp_creds=$(mktemp)
|
||||
cat > "$temp_creds" <<EOF
|
||||
{
|
||||
"AccountTag": "${CLOUDFLARE_ACCOUNT_ID:-}",
|
||||
"TunnelSecret": "${TUNNEL_TOKEN}",
|
||||
"TunnelID": "${TUNNEL_ID}",
|
||||
"TunnelName": "${TUNNELS[$TUNNEL_NAME]}"
|
||||
}
|
||||
EOF
|
||||
|
||||
# Copy to container
|
||||
log_info "Copying credentials to VMID $VMID..."
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$temp_creds" "/etc/cloudflared/tunnel-${TUNNEL_NAME}.json"
|
||||
else
|
||||
scp "$temp_creds" "root@${PROXMOX_HOST}:/tmp/tunnel-${TUNNEL_NAME}.json"
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/tunnel-${TUNNEL_NAME}.json /etc/cloudflared/tunnel-${TUNNEL_NAME}.json"
|
||||
fi
|
||||
|
||||
# Set permissions
|
||||
exec_in_container "chmod 600 /etc/cloudflared/tunnel-${TUNNEL_NAME}.json"
|
||||
log_success "Credentials saved"
|
||||
|
||||
# Update config file with tunnel ID
|
||||
log_info "Updating config file with tunnel ID..."
|
||||
config_file="$TUNNELS_DIR/configs/tunnel-${TUNNEL_NAME}.yml"
|
||||
|
||||
if [ -f "$config_file" ]; then
|
||||
# Update tunnel ID in config
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
sed -i "s/<TUNNEL_ID_${TUNNEL_NAME^^}>/$TUNNEL_ID/g" "$config_file"
|
||||
pct push "$VMID" "$config_file" "/etc/cloudflared/tunnel-${TUNNEL_NAME}.yml"
|
||||
else
|
||||
sed "s/<TUNNEL_ID_${TUNNEL_NAME^^}>/$TUNNEL_ID/g" "$config_file" > "/tmp/tunnel-${TUNNEL_NAME}.yml"
|
||||
scp "/tmp/tunnel-${TUNNEL_NAME}.yml" "root@${PROXMOX_HOST}:/tmp/"
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/tunnel-${TUNNEL_NAME}.yml /etc/cloudflared/tunnel-${TUNNEL_NAME}.yml"
|
||||
fi
|
||||
log_success "Config file updated"
|
||||
else
|
||||
log_warn "Config file not found: $config_file"
|
||||
fi
|
||||
|
||||
# Cleanup
|
||||
rm -f "$temp_creds"
|
||||
|
||||
log_success "Credentials and config saved for tunnel-${TUNNEL_NAME}"
|
||||
|
||||
132
scripts/cloudflare-tunnels/scripts/set-access-emails.sh
Executable file
132
scripts/cloudflare-tunnels/scripts/set-access-emails.sh
Executable file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env bash
|
||||
# Simple script to set allowed email addresses for Cloudflare Access
|
||||
# Usage: ./set-access-emails.sh email1@example.com email2@example.com ...
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
# Load .env
|
||||
if [ -f "$TUNNELS_DIR/../../.env" ]; then
|
||||
source "$TUNNELS_DIR/../../.env" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
if [[ -z "${CLOUDFLARE_ACCOUNT_ID:-}" ]] || [[ -z "${CLOUDFLARE_API_KEY:-}" ]] || [[ -z "${CLOUDFLARE_EMAIL:-}" ]]; then
|
||||
log_error "Cloudflare credentials not found in .env"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Get emails from command line
|
||||
ALLOWED_EMAILS=("$@")
|
||||
|
||||
if [ ${#ALLOWED_EMAILS[@]} -eq 0 ]; then
|
||||
log_error "Usage: $0 email1@example.com email2@example.com ..."
|
||||
echo ""
|
||||
log_info "Example:"
|
||||
echo " $0 admin@example.com user1@example.com user2@example.com"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Configuring Access policies for: ${ALLOWED_EMAILS[*]}"
|
||||
echo ""
|
||||
|
||||
# App IDs (from earlier creation)
|
||||
declare -A APP_IDS=(
|
||||
["ml110-01.d-bis.org"]="ebc7cafa-11dc-4bfa-8347-4e6c229f4d3b"
|
||||
["r630-01.d-bis.org"]="967625a2-0199-490a-9f4f-2de5c8d49243"
|
||||
["r630-02.d-bis.org"]="618ab003-37bf-413e-b0fa-13963c2186c5"
|
||||
)
|
||||
|
||||
# Function to make API request
|
||||
cf_api_request() {
|
||||
local method="$1"
|
||||
local endpoint="$2"
|
||||
local data="${3:-}"
|
||||
|
||||
local url="https://api.cloudflare.com/client/v4${endpoint}"
|
||||
local temp_file=$(mktemp)
|
||||
local http_code
|
||||
|
||||
if [[ -n "$data" ]]; then
|
||||
http_code=$(curl -s -o "$temp_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
|
||||
-H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" 2>/dev/null)
|
||||
else
|
||||
http_code=$(curl -s -o "$temp_file" -w "%{http_code}" \
|
||||
-X "$method" "$url" \
|
||||
-H "X-Auth-Email: ${CLOUDFLARE_EMAIL}" \
|
||||
-H "X-Auth-Key: ${CLOUDFLARE_API_KEY}" \
|
||||
-H "Content-Type: application/json" 2>/dev/null)
|
||||
fi
|
||||
|
||||
local response=$(cat "$temp_file" 2>/dev/null || echo "")
|
||||
rm -f "$temp_file"
|
||||
|
||||
if [[ "$http_code" != "200" ]] && [[ "$http_code" != "201" ]]; then
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "$response"
|
||||
}
|
||||
|
||||
# Build email includes
|
||||
EMAIL_INCLUDES=$(printf '%s\n' "${ALLOWED_EMAILS[@]}" | jq -R . | jq -s . | jq 'map({email: {email: .}})')
|
||||
|
||||
# Configure each app
|
||||
for hostname in "${!APP_IDS[@]}"; do
|
||||
app_id="${APP_IDS[$hostname]}"
|
||||
|
||||
log_info "Configuring $hostname..."
|
||||
|
||||
# Get existing policies
|
||||
POLICIES=$(cf_api_request "GET" "/accounts/${CLOUDFLARE_ACCOUNT_ID}/access/apps/${app_id}/policies" 2>&1) || POLICIES="{}"
|
||||
EXISTING_ID=$(echo "$POLICIES" | jq -r '.result[]? | select(.name == "Allow Team Access") | .id' 2>/dev/null || echo "")
|
||||
|
||||
# Build policy (require field removed - email verification is default)
|
||||
POLICY_DATA=$(jq -n \
|
||||
--argjson emails "$EMAIL_INCLUDES" \
|
||||
'{
|
||||
name: "Allow Team Access",
|
||||
decision: "allow",
|
||||
include: $emails
|
||||
}')
|
||||
|
||||
if [[ -n "$EXISTING_ID" ]] && [[ "$EXISTING_ID" != "null" ]]; then
|
||||
# Update
|
||||
response=$(cf_api_request "PUT" "/accounts/${CLOUDFLARE_ACCOUNT_ID}/access/apps/${app_id}/policies/${EXISTING_ID}" "$POLICY_DATA" 2>&1)
|
||||
else
|
||||
# Create
|
||||
response=$(cf_api_request "POST" "/accounts/${CLOUDFLARE_ACCOUNT_ID}/access/apps/${app_id}/policies" "$POLICY_DATA" 2>&1)
|
||||
fi
|
||||
|
||||
if echo "$response" | jq -e '.success' >/dev/null 2>&1; then
|
||||
log_success " ✓ $hostname configured"
|
||||
else
|
||||
log_error " ✗ Failed for $hostname"
|
||||
echo "$response" | jq -r '.errors[0].message // "Unknown error"' 2>/dev/null || echo "$response" | head -3
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
log_success "=== Access Policies Configured ==="
|
||||
log_info "Allowed emails:"
|
||||
for email in "${ALLOWED_EMAILS[@]}"; do
|
||||
echo " - $email"
|
||||
done
|
||||
|
||||
178
scripts/cloudflare-tunnels/scripts/setup-credentials-auto.sh
Executable file
178
scripts/cloudflare-tunnels/scripts/setup-credentials-auto.sh
Executable file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env bash
|
||||
# Automated credentials setup - checks for files and sets up everything
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
|
||||
declare -A TUNNELS=(
|
||||
["ml110"]="tunnel-ml110:ccd7150a-9881-4b8c-a105-9b4ead6e69a2"
|
||||
["r630-01"]="tunnel-r630-01:4481af8f-b24c-4cd3-bdd5-f562f4c97df4"
|
||||
["r630-02"]="tunnel-r630-02:0876f12b-64d7-4927-9ab3-94cb6cf48af9"
|
||||
)
|
||||
|
||||
# Check if running on Proxmox host
|
||||
if command -v pct &> /dev/null; then
|
||||
RUN_LOCAL=true
|
||||
else
|
||||
RUN_LOCAL=false
|
||||
fi
|
||||
|
||||
exec_in_container() {
|
||||
local cmd="$1"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct exec "$VMID" -- bash -c "$cmd"
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "pct exec $VMID -- bash -c '$cmd'"
|
||||
fi
|
||||
}
|
||||
|
||||
copy_to_container() {
|
||||
local src="$1"
|
||||
local dst="$2"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$src" "$dst"
|
||||
else
|
||||
scp "$src" "root@${PROXMOX_HOST}:/tmp/$(basename "$src")" >/dev/null 2>&1
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/$(basename "$src") $dst" >/dev/null 2>&1
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "=== Automated Credentials Setup ==="
|
||||
echo ""
|
||||
|
||||
# Check for credentials files in current directory
|
||||
MISSING_FILES=()
|
||||
FOUND_FILES=()
|
||||
|
||||
for tunnel_key in "${!TUNNELS[@]}"; do
|
||||
IFS=':' read -r tunnel_name tunnel_id <<< "${TUNNELS[$tunnel_key]}"
|
||||
|
||||
# Try multiple possible file names
|
||||
creds_file=""
|
||||
for possible_name in "credentials-${tunnel_key}.json" "credentials-${tunnel_name}.json" "${tunnel_name}.json"; do
|
||||
if [ -f "$TUNNELS_DIR/$possible_name" ]; then
|
||||
creds_file="$TUNNELS_DIR/$possible_name"
|
||||
break
|
||||
fi
|
||||
if [ -f "$possible_name" ]; then
|
||||
creds_file="$possible_name"
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
if [[ -n "$creds_file" ]] && [[ -f "$creds_file" ]]; then
|
||||
FOUND_FILES+=("$tunnel_key:$creds_file")
|
||||
log_success "Found credentials for $tunnel_name: $creds_file"
|
||||
else
|
||||
MISSING_FILES+=("$tunnel_key:$tunnel_name:$tunnel_id")
|
||||
log_warn "Missing credentials for $tunnel_name"
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
|
||||
# If some files are missing, provide instructions
|
||||
if [ ${#MISSING_FILES[@]} -gt 0 ]; then
|
||||
log_error "Missing credentials files:"
|
||||
for entry in "${MISSING_FILES[@]}"; do
|
||||
IFS=':' read -r key name id <<< "$entry"
|
||||
echo " - $name (ID: $id)"
|
||||
done
|
||||
echo ""
|
||||
log_info "To download credentials:"
|
||||
echo " 1. Go to: https://one.dash.cloudflare.com/ → Zero Trust → Networks → Tunnels"
|
||||
echo " 2. Click each tunnel → Configure → Download credentials file"
|
||||
echo " 3. Save as: credentials-${key}.json in $TUNNELS_DIR"
|
||||
echo " 4. Run this script again"
|
||||
echo ""
|
||||
|
||||
if [ ${#FOUND_FILES[@]} -eq 0 ]; then
|
||||
log_error "No credentials files found. Cannot proceed."
|
||||
exit 1
|
||||
else
|
||||
log_warn "Proceeding with available credentials only..."
|
||||
echo ""
|
||||
fi
|
||||
fi
|
||||
|
||||
# Process found files
|
||||
if [ ${#FOUND_FILES[@]} -eq 0 ]; then
|
||||
log_error "No credentials files found!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
log_info "Setting up credentials for ${#FOUND_FILES[@]} tunnel(s)..."
|
||||
echo ""
|
||||
|
||||
# Ensure /etc/cloudflared exists in container
|
||||
exec_in_container "mkdir -p /etc/cloudflared"
|
||||
|
||||
for entry in "${FOUND_FILES[@]}"; do
|
||||
IFS=':' read -r tunnel_key creds_file <<< "$entry"
|
||||
IFS=':' read -r tunnel_name tunnel_id <<< "${TUNNELS[$tunnel_key]}"
|
||||
|
||||
log_info "Processing $tunnel_name..."
|
||||
|
||||
# Validate JSON structure
|
||||
if ! jq -e '.AccountTag, .TunnelSecret, .TunnelID' "$creds_file" >/dev/null 2>&1; then
|
||||
log_error "Invalid credentials file format: $creds_file"
|
||||
log_info "Expected format: { \"AccountTag\": \"...\", \"TunnelSecret\": \"...\", \"TunnelID\": \"...\" }"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Copy credentials to container
|
||||
log_info " Copying credentials to VMID $VMID..."
|
||||
copy_to_container "$creds_file" "/etc/cloudflared/credentials-${tunnel_key}.json"
|
||||
|
||||
# Set permissions
|
||||
exec_in_container "chmod 600 /etc/cloudflared/credentials-${tunnel_key}.json"
|
||||
|
||||
# Update config file
|
||||
config_file="$TUNNELS_DIR/configs/tunnel-${tunnel_key}.yml"
|
||||
if [ -f "$config_file" ]; then
|
||||
log_info " Updating config file..."
|
||||
temp_config=$(mktemp)
|
||||
|
||||
# Replace tunnel ID placeholder
|
||||
sed "s/<TUNNEL_ID_${tunnel_key^^}>/$tunnel_id/g" "$config_file" > "$temp_config"
|
||||
|
||||
# Ensure credentials path is correct
|
||||
sed -i "s|credentials-file:.*|credentials-file: /etc/cloudflared/credentials-${tunnel_key}.json|g" "$temp_config"
|
||||
|
||||
# Copy config to container
|
||||
copy_to_container "$temp_config" "/etc/cloudflared/tunnel-${tunnel_key}.yml"
|
||||
rm -f "$temp_config"
|
||||
|
||||
log_success " ✓ $tunnel_name configured"
|
||||
else
|
||||
log_warn " Config file not found: $config_file"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
done
|
||||
|
||||
log_success "=== Setup Complete ==="
|
||||
echo ""
|
||||
log_info "Next steps:"
|
||||
log_info "1. Verify: ssh root@${PROXMOX_HOST} 'pct exec $VMID -- ls -la /etc/cloudflared/'"
|
||||
log_info "2. Start services: ssh root@${PROXMOX_HOST} 'pct exec $VMID -- systemctl start cloudflared-*'"
|
||||
log_info "3. Check status: ssh root@${PROXMOX_HOST} 'pct exec $VMID -- systemctl status cloudflared-*'"
|
||||
log_info "4. Enable on boot: ssh root@${PROXMOX_HOST} 'pct exec $VMID -- systemctl enable cloudflared-*'"
|
||||
|
||||
203
scripts/cloudflare-tunnels/scripts/setup-multi-tunnel.sh
Executable file
203
scripts/cloudflare-tunnels/scripts/setup-multi-tunnel.sh
Executable file
@@ -0,0 +1,203 @@
|
||||
#!/usr/bin/env bash
|
||||
# Setup script for Cloudflare Multi-Tunnel configuration
|
||||
# This script sets up separate tunnels for each Proxmox host
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
PROJECT_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[WARN]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[ERROR]${NC} $1"; }
|
||||
|
||||
# Configuration
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
TUNNELS=("ml110" "r630-01" "r630-02")
|
||||
|
||||
# Check if running on Proxmox host or need to SSH
|
||||
if command -v pct &> /dev/null; then
|
||||
RUN_LOCAL=true
|
||||
log_info "Running on Proxmox host directly"
|
||||
else
|
||||
RUN_LOCAL=false
|
||||
log_info "Will execute commands via SSH to $PROXMOX_HOST"
|
||||
fi
|
||||
|
||||
# Function to execute command (local or via SSH)
|
||||
exec_cmd() {
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
eval "$@"
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "$@"
|
||||
fi
|
||||
}
|
||||
|
||||
# Function to execute command in container
|
||||
exec_in_container() {
|
||||
local cmd="$1"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct exec "$VMID" -- bash -c "$cmd"
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "pct exec $VMID -- bash -c '$cmd'"
|
||||
fi
|
||||
}
|
||||
|
||||
log_info "=== Cloudflare Multi-Tunnel Setup ==="
|
||||
log_info "Proxmox Host: $PROXMOX_HOST"
|
||||
log_info "VMID: $VMID"
|
||||
log_info "Tunnels: ${TUNNELS[*]}"
|
||||
echo ""
|
||||
|
||||
# Check if VMID 102 exists and is running
|
||||
log_info "Checking VMID $VMID status..."
|
||||
if ! exec_cmd "pct status $VMID 2>/dev/null | grep -q running"; then
|
||||
log_error "VMID $VMID is not running. Please start it first."
|
||||
exit 1
|
||||
fi
|
||||
log_success "VMID $VMID is running"
|
||||
|
||||
# Check if cloudflared is installed
|
||||
log_info "Checking cloudflared installation..."
|
||||
if ! exec_in_container "command -v cloudflared &> /dev/null"; then
|
||||
log_warn "cloudflared not found. Installing..."
|
||||
exec_in_container "
|
||||
wget -q https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64.deb -O /tmp/cloudflared.deb
|
||||
dpkg -i /tmp/cloudflared.deb || apt-get install -f -y
|
||||
rm /tmp/cloudflared.deb
|
||||
cloudflared --version
|
||||
"
|
||||
log_success "cloudflared installed"
|
||||
else
|
||||
log_success "cloudflared is installed"
|
||||
fi
|
||||
|
||||
# Create directories
|
||||
log_info "Creating configuration directories..."
|
||||
exec_in_container "
|
||||
mkdir -p /etc/cloudflared
|
||||
mkdir -p /var/log/cloudflared
|
||||
"
|
||||
log_success "Directories created"
|
||||
|
||||
# Copy configuration files
|
||||
log_info "Copying configuration files..."
|
||||
for tunnel in "${TUNNELS[@]}"; do
|
||||
config_file="$TUNNELS_DIR/configs/tunnel-${tunnel}.yml"
|
||||
|
||||
if [ ! -f "$config_file" ]; then
|
||||
log_error "Configuration file not found: $config_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy to container
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$config_file" "/etc/cloudflared/tunnel-${tunnel}.yml"
|
||||
else
|
||||
scp "$config_file" "root@${PROXMOX_HOST}:/tmp/tunnel-${tunnel}.yml"
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/tunnel-${tunnel}.yml /etc/cloudflared/tunnel-${tunnel}.yml"
|
||||
fi
|
||||
|
||||
log_success "Copied config for tunnel-${tunnel}"
|
||||
done
|
||||
|
||||
# Copy systemd service files
|
||||
log_info "Installing systemd service files..."
|
||||
for tunnel in "${TUNNELS[@]}"; do
|
||||
service_file="$TUNNELS_DIR/systemd/cloudflared-${tunnel}.service"
|
||||
|
||||
if [ ! -f "$service_file" ]; then
|
||||
log_error "Service file not found: $service_file"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy to container
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$service_file" "/tmp/cloudflared-${tunnel}.service"
|
||||
exec_in_container "mv /tmp/cloudflared-${tunnel}.service /etc/systemd/system/cloudflared-${tunnel}.service"
|
||||
else
|
||||
scp "$service_file" "root@${PROXMOX_HOST}:/tmp/cloudflared-${tunnel}.service"
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/cloudflared-${tunnel}.service /etc/systemd/system/cloudflared-${tunnel}.service"
|
||||
exec_in_container "mv /tmp/cloudflared-${tunnel}.service /etc/systemd/system/cloudflared-${tunnel}.service"
|
||||
fi
|
||||
|
||||
log_success "Installed service for tunnel-${tunnel}"
|
||||
done
|
||||
|
||||
# Reload systemd
|
||||
log_info "Reloading systemd..."
|
||||
exec_in_container "systemctl daemon-reload"
|
||||
log_success "Systemd reloaded"
|
||||
|
||||
# Prompt for tunnel tokens
|
||||
log_warn "=== IMPORTANT: Tunnel Setup Required ==="
|
||||
log_warn "Before enabling services, you need to:"
|
||||
log_warn "1. Create tunnels in Cloudflare Dashboard"
|
||||
log_warn "2. Copy tunnel tokens/credentials"
|
||||
log_warn "3. Update configuration files with tunnel IDs"
|
||||
log_warn "4. Place credential files in /etc/cloudflared/"
|
||||
echo ""
|
||||
log_info "See docs/CLOUDFLARE_ACCESS_SETUP.md for detailed instructions"
|
||||
echo ""
|
||||
|
||||
read -p "Have you created the tunnels and have the credentials ready? (y/N): " -n 1 -r
|
||||
echo
|
||||
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
|
||||
log_warn "Setup paused. Please create tunnels first."
|
||||
log_info "Run this script again after creating tunnels."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# Prompt for each tunnel
|
||||
for tunnel in "${TUNNELS[@]}"; do
|
||||
echo ""
|
||||
log_info "=== Setting up tunnel-${tunnel} ==="
|
||||
|
||||
read -p "Enter tunnel ID for tunnel-${tunnel}: " tunnel_id
|
||||
read -p "Enter path to credentials JSON file (or press Enter to skip): " creds_file
|
||||
|
||||
if [ -n "$creds_file" ] && [ -f "$creds_file" ]; then
|
||||
# Update config file with tunnel ID
|
||||
exec_in_container "sed -i 's/<TUNNEL_ID_${tunnel^^}>/$tunnel_id/g' /etc/cloudflared/tunnel-${tunnel}.yml"
|
||||
|
||||
# Copy credentials file
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct push "$VMID" "$creds_file" "/etc/cloudflared/tunnel-${tunnel}.json"
|
||||
else
|
||||
scp "$creds_file" "root@${PROXMOX_HOST}:/tmp/tunnel-${tunnel}.json"
|
||||
ssh "root@${PROXMOX_HOST}" "pct push $VMID /tmp/tunnel-${tunnel}.json /etc/cloudflared/tunnel-${tunnel}.json"
|
||||
fi
|
||||
|
||||
exec_in_container "chmod 600 /etc/cloudflared/tunnel-${tunnel}.json"
|
||||
log_success "Credentials configured for tunnel-${tunnel}"
|
||||
else
|
||||
log_warn "Skipping credentials for tunnel-${tunnel}. Configure manually later."
|
||||
fi
|
||||
done
|
||||
|
||||
# Enable services (but don't start yet - user should verify configs first)
|
||||
log_info "Enabling systemd services..."
|
||||
for tunnel in "${TUNNELS[@]}"; do
|
||||
exec_in_container "systemctl enable cloudflared-${tunnel}.service"
|
||||
log_success "Enabled cloudflared-${tunnel}.service"
|
||||
done
|
||||
|
||||
echo ""
|
||||
log_success "=== Setup Complete ==="
|
||||
log_info "Next steps:"
|
||||
log_info "1. Verify configuration files in /etc/cloudflared/"
|
||||
log_info "2. Start services: systemctl start cloudflared-*"
|
||||
log_info "3. Check status: systemctl status cloudflared-*"
|
||||
log_info "4. Configure Cloudflare Access (see docs/CLOUDFLARE_ACCESS_SETUP.md)"
|
||||
log_info "5. Set up monitoring: ./scripts/monitor-tunnels.sh --daemon"
|
||||
|
||||
165
scripts/cloudflare-tunnels/scripts/verify-prerequisites.sh
Executable file
165
scripts/cloudflare-tunnels/scripts/verify-prerequisites.sh
Executable file
@@ -0,0 +1,165 @@
|
||||
#!/usr/bin/env bash
|
||||
# Verify prerequisites before deployment
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
# Colors
|
||||
RED='\033[0;31m'
|
||||
GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
BLUE='\033[0;34m'
|
||||
NC='\033[0m'
|
||||
|
||||
log_info() { echo -e "${BLUE}[INFO]${NC} $1"; }
|
||||
log_success() { echo -e "${GREEN}[✓]${NC} $1"; }
|
||||
log_warn() { echo -e "${YELLOW}[⚠]${NC} $1"; }
|
||||
log_error() { echo -e "${RED}[✗]${NC} $1"; }
|
||||
|
||||
PROXMOX_HOST="${PROXMOX_HOST:-192.168.11.10}"
|
||||
VMID="${VMID:-102}"
|
||||
|
||||
# Check if running on Proxmox host
|
||||
if command -v pct &> /dev/null; then
|
||||
RUN_LOCAL=true
|
||||
else
|
||||
RUN_LOCAL=false
|
||||
fi
|
||||
|
||||
exec_in_container() {
|
||||
local cmd="$1"
|
||||
if [ "$RUN_LOCAL" = true ]; then
|
||||
pct exec "$VMID" -- bash -c "$cmd" 2>/dev/null || return 1
|
||||
else
|
||||
ssh "root@${PROXMOX_HOST}" "pct exec $VMID -- bash -c '$cmd'" 2>/dev/null || return 1
|
||||
fi
|
||||
}
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Prerequisites Verification"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
|
||||
all_checks_passed=true
|
||||
|
||||
# Check 1: VMID 102 exists
|
||||
log_info "Checking VMID $VMID exists..."
|
||||
if exec_in_container "true"; then
|
||||
log_success "VMID $VMID is accessible"
|
||||
else
|
||||
log_error "VMID $VMID is not accessible"
|
||||
all_checks_passed=false
|
||||
fi
|
||||
|
||||
# Check 2: VMID 102 is running
|
||||
log_info "Checking VMID $VMID is running..."
|
||||
if exec_in_container "systemctl is-system-running > /dev/null 2>&1"; then
|
||||
log_success "VMID $VMID is running"
|
||||
else
|
||||
log_error "VMID $VMID is not running"
|
||||
all_checks_passed=false
|
||||
fi
|
||||
|
||||
# Check 3: Network connectivity to Proxmox hosts
|
||||
log_info "Checking network connectivity to Proxmox hosts..."
|
||||
for ip in 192.168.11.10 192.168.11.11 192.168.11.12; do
|
||||
if exec_in_container "timeout 3 bash -c 'echo > /dev/tcp/${ip}/8006' 2>/dev/null"; then
|
||||
log_success "Can reach ${ip}:8006"
|
||||
else
|
||||
log_warn "Cannot reach ${ip}:8006 (may need to test manually)"
|
||||
fi
|
||||
done
|
||||
|
||||
# Check 4: cloudflared installation
|
||||
log_info "Checking cloudflared installation..."
|
||||
if exec_in_container "command -v cloudflared &> /dev/null"; then
|
||||
version=$(exec_in_container "cloudflared --version 2>/dev/null | head -1" || echo "unknown")
|
||||
log_success "cloudflared is installed: $version"
|
||||
else
|
||||
log_warn "cloudflared is not installed (will be installed by setup script)"
|
||||
fi
|
||||
|
||||
# Check 5: Configuration files exist
|
||||
log_info "Checking configuration files..."
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
TUNNELS_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
|
||||
for tunnel in ml110 r630-01 r630-02; do
|
||||
if [ -f "$TUNNELS_DIR/configs/tunnel-${tunnel}.yml" ]; then
|
||||
log_success "Config file exists: tunnel-${tunnel}.yml"
|
||||
else
|
||||
log_error "Config file missing: tunnel-${tunnel}.yml"
|
||||
all_checks_passed=false
|
||||
fi
|
||||
done
|
||||
|
||||
# Check 6: Systemd service files exist
|
||||
log_info "Checking systemd service files..."
|
||||
for tunnel in ml110 r630-01 r630-02; do
|
||||
if [ -f "$TUNNELS_DIR/systemd/cloudflared-${tunnel}.service" ]; then
|
||||
log_success "Service file exists: cloudflared-${tunnel}.service"
|
||||
else
|
||||
log_error "Service file missing: cloudflared-${tunnel}.service"
|
||||
all_checks_passed=false
|
||||
fi
|
||||
done
|
||||
|
||||
# Check 7: Scripts are executable
|
||||
log_info "Checking scripts are executable..."
|
||||
for script in setup-multi-tunnel.sh install-tunnel.sh monitor-tunnels.sh check-tunnel-health.sh alert-tunnel-failure.sh restart-tunnel.sh; do
|
||||
if [ -x "$SCRIPT_DIR/$script" ]; then
|
||||
log_success "Script is executable: $script"
|
||||
else
|
||||
log_warn "Script not executable: $script (will fix)"
|
||||
chmod +x "$SCRIPT_DIR/$script" 2>/dev/null || true
|
||||
fi
|
||||
done
|
||||
|
||||
# Check 8: Directories exist
|
||||
log_info "Checking directory structure..."
|
||||
for dir in configs systemd scripts monitoring docs; do
|
||||
if [ -d "$TUNNELS_DIR/$dir" ]; then
|
||||
log_success "Directory exists: $dir"
|
||||
else
|
||||
log_error "Directory missing: $dir"
|
||||
all_checks_passed=false
|
||||
fi
|
||||
done
|
||||
|
||||
echo ""
|
||||
echo "=========================================="
|
||||
echo " Manual Prerequisites"
|
||||
echo "=========================================="
|
||||
echo ""
|
||||
log_warn "The following must be done manually:"
|
||||
echo ""
|
||||
echo "1. Cloudflare Account:"
|
||||
echo " - [ ] Cloudflare account with Zero Trust enabled"
|
||||
echo " - [ ] Domain d-bis.org managed by Cloudflare"
|
||||
echo ""
|
||||
echo "2. Create Tunnels in Cloudflare Dashboard:"
|
||||
echo " - [ ] Go to: https://one.dash.cloudflare.com"
|
||||
echo " - [ ] Zero Trust → Networks → Tunnels"
|
||||
echo " - [ ] Create: tunnel-ml110"
|
||||
echo " - [ ] Create: tunnel-r630-01"
|
||||
echo " - [ ] Create: tunnel-r630-02"
|
||||
echo " - [ ] Copy tunnel tokens/credentials"
|
||||
echo ""
|
||||
echo "3. DNS Records (after tunnels created):"
|
||||
echo " - [ ] Create CNAME: ml110-01 → <tunnel-id>.cfargotunnel.com (Proxied)"
|
||||
echo " - [ ] Create CNAME: r630-01 → <tunnel-id>.cfargotunnel.com (Proxied)"
|
||||
echo " - [ ] Create CNAME: r630-02 → <tunnel-id>.cfargotunnel.com (Proxied)"
|
||||
echo ""
|
||||
|
||||
echo "=========================================="
|
||||
if [ "$all_checks_passed" = true ]; then
|
||||
log_success "All automated checks passed!"
|
||||
echo ""
|
||||
log_info "You can proceed with:"
|
||||
echo " ./scripts/setup-multi-tunnel.sh"
|
||||
exit 0
|
||||
else
|
||||
log_error "Some checks failed. Please fix issues before proceeding."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
Reference in New Issue
Block a user