Collection of operational skills for VaultMesh infrastructure including: - backup-sovereign: Backup and recovery operations - btc-anchor: Bitcoin anchoring - cloudflare-tunnel-manager: Cloudflare tunnel management - container-registry: Container registry operations - disaster-recovery: Disaster recovery procedures - dns-sovereign: DNS management - eth-anchor: Ethereum anchoring - gitea-bootstrap: Gitea setup and configuration - hetzner-bootstrap: Hetzner server provisioning - merkle-forest: Merkle tree operations - node-hardening: Node security hardening - operator-bootstrap: Operator initialization - proof-verifier: Cryptographic proof verification - rfc3161-anchor: RFC3161 timestamping - secrets-vault: Secrets management 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
217 lines
6.0 KiB
Bash
Executable File
217 lines
6.0 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
set -euo pipefail
|
|
|
|
# === METADATA ===
|
|
SCRIPT_NAME="$(basename "$0")"
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
|
|
|
|
# === CONFIGURATION ===
|
|
: "${DOMAIN:?DOMAIN required}"
|
|
: "${CF_ACCOUNT_ID:?CF_ACCOUNT_ID required}"
|
|
: "${NODE_NAME:=node-a}"
|
|
: "${TUNNEL_NAME:=$NODE_NAME-tunnel}"
|
|
: "${OUTPUT_DIR:=$SKILL_ROOT/outputs}"
|
|
|
|
# === FUNCTIONS ===
|
|
log_info() { echo "[INFO] $(date -Iseconds) $*"; }
|
|
log_warn() { echo "[WARN] $(date -Iseconds) $*" >&2; }
|
|
log_error() { echo "[ERROR] $(date -Iseconds) $*" >&2; }
|
|
die() { log_error "$@"; exit 1; }
|
|
|
|
preflight() {
|
|
[[ -d "$OUTPUT_DIR" ]] || mkdir -p "$OUTPUT_DIR"
|
|
[[ -d "$HOME/.cloudflared" ]] || mkdir -p "$HOME/.cloudflared"
|
|
}
|
|
|
|
ensure_authenticated() {
|
|
if [[ ! -f "$HOME/.cloudflared/cert.pem" ]]; then
|
|
log_info "No cloudflared certificate found. Starting login..."
|
|
log_info "A browser window will open for authentication."
|
|
cloudflared tunnel login
|
|
else
|
|
log_info "Cloudflared already authenticated"
|
|
fi
|
|
}
|
|
|
|
create_tunnel() {
|
|
local creds="$HOME/.cloudflared/$TUNNEL_NAME.json"
|
|
|
|
if [[ -f "$creds" ]]; then
|
|
log_info "Tunnel credentials already exist - reusing existing tunnel"
|
|
return 0
|
|
fi
|
|
|
|
log_info "Creating Cloudflare tunnel: $TUNNEL_NAME"
|
|
|
|
cloudflared tunnel create "$TUNNEL_NAME"
|
|
|
|
if [[ ! -f "$creds" ]]; then
|
|
die "Tunnel creation failed - credentials not found at $creds"
|
|
fi
|
|
|
|
log_info "Tunnel created successfully"
|
|
}
|
|
|
|
create_config() {
|
|
local config="$HOME/.cloudflared/config-$TUNNEL_NAME.yml"
|
|
|
|
if [[ -f "$config" ]]; then
|
|
log_info "Tunnel config already exists at $config - skipping"
|
|
return 0
|
|
fi
|
|
|
|
cat > "$config" <<EOF
|
|
# Cloudflare Tunnel Configuration
|
|
# Generated by operator-bootstrap $(date -Iseconds)
|
|
# Node: $NODE_NAME
|
|
|
|
tunnel: $TUNNEL_NAME
|
|
credentials-file: $HOME/.cloudflared/$TUNNEL_NAME.json
|
|
|
|
ingress:
|
|
# SSH access via Cloudflare Access
|
|
- hostname: ssh.$DOMAIN
|
|
service: ssh://localhost:22
|
|
|
|
# Default web service (placeholder)
|
|
- hostname: "*.$DOMAIN"
|
|
service: http://localhost:8080
|
|
|
|
# Catch-all 404
|
|
- service: http_status:404
|
|
EOF
|
|
|
|
log_info "Tunnel config created: $config"
|
|
}
|
|
|
|
setup_dns() {
|
|
log_info "Creating DNS route for ssh.$DOMAIN..."
|
|
|
|
# This may fail if DNS already exists - that's OK
|
|
if cloudflared tunnel route dns "$TUNNEL_NAME" "ssh.$DOMAIN" 2>/dev/null; then
|
|
log_info "DNS route created for ssh.$DOMAIN"
|
|
else
|
|
log_warn "DNS route creation returned error - may already exist"
|
|
log_info "Verify DNS at: https://dash.cloudflare.com"
|
|
fi
|
|
}
|
|
|
|
store_credentials_in_pass() {
|
|
local creds="$HOME/.cloudflared/$TUNNEL_NAME.json"
|
|
|
|
if command -v pass &>/dev/null && [[ -d "$HOME/.password-store" ]]; then
|
|
log_info "Storing tunnel ID in pass..."
|
|
|
|
local tunnel_id
|
|
if command -v jq &>/dev/null; then
|
|
tunnel_id=$(jq -r '.TunnelID' "$creds" 2>/dev/null || echo "unknown")
|
|
else
|
|
tunnel_id=$(grep -o '"TunnelID":"[^"]*"' "$creds" | cut -d'"' -f4 || echo "unknown")
|
|
fi
|
|
|
|
echo "$tunnel_id" | pass insert -f "infrastructure/cloudflare/tunnel-id" 2>/dev/null || \
|
|
log_warn "Could not store tunnel ID in pass"
|
|
|
|
log_info "Tunnel ID stored in pass: infrastructure/cloudflare/tunnel-id"
|
|
else
|
|
log_warn "Pass not available - tunnel ID stored only in $creds"
|
|
fi
|
|
}
|
|
|
|
install_service() {
|
|
local config="$HOME/.cloudflared/config-$TUNNEL_NAME.yml"
|
|
|
|
log_info "Attempting to install systemd service..."
|
|
|
|
# Try user service first (doesn't require root)
|
|
if systemctl --user daemon-reload 2>/dev/null; then
|
|
# Create user service file
|
|
local service_dir="$HOME/.config/systemd/user"
|
|
mkdir -p "$service_dir"
|
|
|
|
cat > "$service_dir/cloudflared-$TUNNEL_NAME.service" <<EOF
|
|
[Unit]
|
|
Description=Cloudflare Tunnel $TUNNEL_NAME
|
|
After=network-online.target
|
|
Wants=network-online.target
|
|
|
|
[Service]
|
|
Type=simple
|
|
ExecStart=$(command -v cloudflared) tunnel --config $config run
|
|
Restart=on-failure
|
|
RestartSec=5
|
|
|
|
[Install]
|
|
WantedBy=default.target
|
|
EOF
|
|
|
|
systemctl --user daemon-reload
|
|
systemctl --user enable "cloudflared-$TUNNEL_NAME.service"
|
|
log_info "User service installed: cloudflared-$TUNNEL_NAME.service"
|
|
log_info "Start with: systemctl --user start cloudflared-$TUNNEL_NAME"
|
|
else
|
|
log_warn "Could not set up user systemd service"
|
|
log_info "To run tunnel manually: cloudflared tunnel --config $config run"
|
|
fi
|
|
}
|
|
|
|
generate_manifest() {
|
|
local manifest="$OUTPUT_DIR/tunnel_config.json"
|
|
local creds="$HOME/.cloudflared/$TUNNEL_NAME.json"
|
|
|
|
local tunnel_id="unknown"
|
|
if command -v jq &>/dev/null && [[ -f "$creds" ]]; then
|
|
tunnel_id=$(jq -r '.TunnelID' "$creds" 2>/dev/null || echo "unknown")
|
|
elif [[ -f "$creds" ]]; then
|
|
tunnel_id=$(grep -o '"TunnelID":"[^"]*"' "$creds" | cut -d'"' -f4 || echo "unknown")
|
|
fi
|
|
|
|
cat > "$manifest" <<EOF
|
|
{
|
|
"timestamp": "$(date -Iseconds)",
|
|
"node": "$NODE_NAME",
|
|
"tunnel": {
|
|
"name": "$TUNNEL_NAME",
|
|
"id": "$tunnel_id",
|
|
"credentials": "$HOME/.cloudflared/$TUNNEL_NAME.json",
|
|
"config": "$HOME/.cloudflared/config-$TUNNEL_NAME.yml"
|
|
},
|
|
"dns": {
|
|
"ssh": "ssh.$DOMAIN"
|
|
},
|
|
"service": "cloudflared-$TUNNEL_NAME"
|
|
}
|
|
EOF
|
|
log_info "Tunnel manifest written to $manifest"
|
|
}
|
|
|
|
main() {
|
|
preflight
|
|
log_info "Starting $SCRIPT_NAME..."
|
|
|
|
ensure_authenticated
|
|
create_tunnel
|
|
create_config
|
|
setup_dns
|
|
store_credentials_in_pass
|
|
install_service
|
|
generate_manifest
|
|
|
|
echo ""
|
|
echo "============================================"
|
|
echo " TUNNEL SETUP COMPLETE"
|
|
echo "============================================"
|
|
echo ""
|
|
echo " Tunnel: $TUNNEL_NAME"
|
|
echo " SSH access: ssh.$DOMAIN"
|
|
echo ""
|
|
echo " To start: systemctl --user start cloudflared-$TUNNEL_NAME"
|
|
echo " To test: cloudflared tunnel --config ~/.cloudflared/config-$TUNNEL_NAME.yml run"
|
|
echo ""
|
|
|
|
log_info "Completed $SCRIPT_NAME"
|
|
}
|
|
|
|
[[ "${BASH_SOURCE[0]}" == "$0" ]] && main "$@"
|