Initial commit: VaultMesh Skills collection

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>
This commit is contained in:
Vault Sovereign
2025-12-27 00:25:00 +00:00
commit eac77ef7b4
213 changed files with 11724 additions and 0 deletions

View File

@@ -0,0 +1,105 @@
---
name: cloudflare-tunnel-manager
description: >
Plan/apply/rollback for Cloudflare Tunnel lifecycle (create, configure,
route DNS, run as service). Includes DRY_RUN safety gates, status matrix,
and audit report. Triggers: 'cloudflare tunnel', 'create tunnel', 'tunnel plan',
'tunnel rollback', 'cloudflared config', 'dns route'.
version: 1.0.0
---
# Cloudflare Tunnel Manager
Tier 1 skill for managing **Cloudflare Tunnels** safely:
- **Plan → Apply** workflow (two-phase)
- **Rollback** scripts for DNS route, service, and tunnel delete
- Verification + audit report
Designed for sovereign Node A style setups where you terminate TLS at Cloudflare
and route traffic to a local service over a tunnel.
## Quick Start
```bash
cd ~/.claude/skills/cloudflare-tunnel-manager
# Required
export CF_API_TOKEN="..." # Cloudflare API token
export CF_ACCOUNT_ID="..." # Cloudflare account ID
# Tunnel identity
export TUNNEL_NAME="node-a-tunnel"
export ZONE_NAME="example.com" # domain in Cloudflare
export HOSTNAME="node-a.example.com"
# Local origin (what tunnel forwards to)
export LOCAL_SERVICE="http://127.0.0.1:9110"
# Safety
export DRY_RUN=1
export REQUIRE_CONFIRM=1
export CONFIRM_PHRASE="I UNDERSTAND THIS CAN CHANGE DNS AND TUNNEL ROUTES"
./scripts/00_preflight.sh
./scripts/10_tunnel_plan.sh
./scripts/20_dns_plan.sh
./scripts/30_service_plan.sh
export DRY_RUN=0
./scripts/11_tunnel_apply.sh
./scripts/21_dns_apply.sh
./scripts/31_service_apply.sh
./scripts/90_verify.sh
./scripts/99_report.sh
```
## Inputs
| Parameter | Required | Default | Description |
|---|---:|---|---|
| CF_API_TOKEN | Yes | (none) | Cloudflare API token with Tunnel + DNS permissions |
| CF_ACCOUNT_ID | Yes | (none) | Cloudflare account ID |
| TUNNEL_NAME | Yes | (none) | Tunnel name |
| ZONE_NAME | Yes | (none) | Zone/domain in Cloudflare (e.g., example.com) |
| HOSTNAME | Yes | (none) | DNS hostname to route (e.g., node-a.example.com) |
| LOCAL_SERVICE | Yes | (none) | Local origin URL (e.g., http://127.0.0.1:9110) |
| CONFIG_DIR | No | outputs/config | Where generated config lives |
| SERVICE_NAME | No | cloudflared-tunnel | systemd unit name |
| DRY_RUN | No | 1 | Apply scripts refuse unless DRY_RUN=0 |
| REQUIRE_CONFIRM | No | 1 | Require confirmation phrase |
| CONFIRM_PHRASE | No | I UNDERSTAND THIS CAN CHANGE DNS AND TUNNEL ROUTES | Safety phrase |
## Outputs
- `outputs/config/config.yml` (generated cloudflared config)
- `outputs/config/tunnel.json` (tunnel metadata snapshot)
- `outputs/status_matrix.json`
- `outputs/audit_report.md`
## Safety Guarantees
1. Default **DRY_RUN=1**
2. Confirmation phrase required for apply and rollback
3. Plan scripts print exact commands and expected changes
4. Rollbacks available:
- DNS route removal
- systemd service stop/disable
- tunnel delete (optional)
## Notes
- This skill uses `cloudflared` CLI.
- You can run the tunnel without systemd (manual) if desired.
## EU Compliance
| Aspect | Value |
|---|---|
| Data Residency | EU (Ireland - Dublin) |
| Jurisdiction | Irish Law |
| Transport | Encrypted tunnel (Cloudflare) |
| Logs | Local status + reports only |
## References
- [Cloudflare Tunnel Notes](references/cloudflare_tunnel_notes.md)

View File

@@ -0,0 +1,17 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SKILL_ROOT/scripts/_common.sh"
: "${SERVICE_NAME:=cloudflared-tunnel}"
main() {
if command -v systemctl >/dev/null 2>&1; then
systemctl is-active "$SERVICE_NAME" >/dev/null 2>&1 || die "Service not active: $SERVICE_NAME"
log_info "Service active: $SERVICE_NAME"
else
die "systemctl not available"
fi
}
main "$@"

View File

@@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SKILL_ROOT/scripts/_common.sh"
main() {
need cloudflared
need curl
need jq
log_info "Tools OK."
}
main "$@"

View File

@@ -0,0 +1,61 @@
{
"name": "cloudflare-tunnel-manager",
"version": "1.0.0",
"description": "Two-phase plan/apply/rollback management for Cloudflare Tunnel lifecycle.",
"defaults": {
"CONFIG_DIR": "outputs/config",
"SERVICE_NAME": "cloudflared-tunnel",
"DRY_RUN": "1",
"REQUIRE_CONFIRM": "1",
"CONFIRM_PHRASE": "I UNDERSTAND THIS CAN CHANGE DNS AND TUNNEL ROUTES"
},
"phases": {
"preflight": [
"00_preflight.sh"
],
"tunnel": {
"plan": [
"10_tunnel_plan.sh"
],
"apply": [
"11_tunnel_apply.sh"
],
"rollback": [
"rollback/undo_tunnel.sh"
]
},
"dns": {
"plan": [
"20_dns_plan.sh"
],
"apply": [
"21_dns_apply.sh"
],
"rollback": [
"rollback/undo_dns.sh"
]
},
"service": {
"plan": [
"30_service_plan.sh"
],
"apply": [
"31_service_apply.sh"
],
"rollback": [
"rollback/undo_service.sh"
]
},
"verify": [
"90_verify.sh"
],
"report": [
"99_report.sh"
]
},
"eu_compliance": {
"data_residency": "EU",
"jurisdiction": "Ireland",
"gdpr_applicable": true
}
}

View File

@@ -0,0 +1,21 @@
# Cloudflare Tunnel Notes
## API Token Permissions (recommended)
- Account: Cloudflare Tunnel (read/edit)
- Zone: DNS (read/edit)
## Credentials
`cloudflared tunnel create` generates a credentials JSON file under:
`~/.cloudflared/<tunnel-id>.json`
This skill's generated `config.yml` references that file directly.
## Ingress
Default pattern:
- Hostname -> LOCAL_SERVICE
- Fallback -> 404
## Rollback Order
1. Stop/disable service
2. Remove DNS route
3. Delete tunnel (optional)

View File

@@ -0,0 +1,35 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/_common.sh"
: "${CF_API_TOKEN:=}"
: "${CF_ACCOUNT_ID:=}"
: "${TUNNEL_NAME:=}"
: "${ZONE_NAME:=}"
: "${HOSTNAME:=}"
: "${LOCAL_SERVICE:=}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
: "${SERVICE_NAME:=cloudflared-tunnel}"
main() {
log_info "Starting 00_preflight.sh"
cf_env_check
[[ -n "$TUNNEL_NAME" ]] || die "TUNNEL_NAME is required."
[[ -n "$ZONE_NAME" ]] || die "ZONE_NAME is required."
[[ -n "$HOSTNAME" ]] || die "HOSTNAME is required."
[[ -n "$LOCAL_SERVICE" ]] || die "LOCAL_SERVICE is required."
need cloudflared
need curl
need jq
need systemctl || log_warn "systemctl not found (service phase will not work)."
mkdir -p "$SKILL_ROOT/outputs"
mkdir -p "$CONFIG_DIR"
log_info "Preflight OK."
}
main "$@"

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/_common.sh"
: "${TUNNEL_NAME:=}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
main() {
[[ -n "$TUNNEL_NAME" ]] || die "TUNNEL_NAME is required."
echo "[PLAN] $(date -Iseconds) Tunnel plan"
echo "[PLAN] Ensure a tunnel exists named: $TUNNEL_NAME"
echo "[PLAN] If missing, create:"
echo " cloudflared tunnel create \"$TUNNEL_NAME\""
echo "[PLAN] Capture tunnel id + credentials to:"
echo " $CONFIG_DIR/tunnel.json and $CONFIG_DIR/<id>.json"
echo "[PLAN] Next: ./scripts/11_tunnel_apply.sh (requires DRY_RUN=0)"
}
main "$@"

View File

@@ -0,0 +1,55 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/_common.sh"
: "${CF_API_TOKEN:=}"
: "${CF_ACCOUNT_ID:=}"
: "${TUNNEL_NAME:=}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
main() {
confirm_gate
cf_env_check
[[ -n "$TUNNEL_NAME" ]] || die "TUNNEL_NAME is required."
mkdir -p "$CONFIG_DIR"
# List tunnels and locate by name
log_info "Looking for existing tunnel: $TUNNEL_NAME"
local list_json
list_json="$(cloudflared tunnel --origincert /dev/null list --output json 2>/dev/null || true)"
# If list command fails (often needs login), fall back to API call
local tunnel_id=""
if [[ -n "$list_json" ]]; then
tunnel_id="$(echo "$list_json" | jq -r --arg n "$TUNNEL_NAME" '.[] | select(.name==$n) | .id' | head -n 1 || true)"
fi
if [[ -z "$tunnel_id" || "$tunnel_id" == "null" ]]; then
log_warn "Tunnel not found via CLI list (or CLI not logged in). Creating tunnel via cloudflared..."
# cloudflared tunnel create requires local credentials; this will prompt if needed.
cloudflared tunnel create "$TUNNEL_NAME"
# After creation, attempt list again
list_json="$(cloudflared tunnel list --output json 2>/dev/null || true)"
tunnel_id="$(echo "$list_json" | jq -r --arg n "$TUNNEL_NAME" '.[] | select(.name==$n) | .id' | head -n 1 || true)"
fi
[[ -n "$tunnel_id" && "$tunnel_id" != "null" ]] || die "Unable to determine tunnel id for $TUNNEL_NAME. Ensure cloudflared is authenticated."
log_info "Tunnel id: $tunnel_id"
# Snapshot tunnel info
cat > "$CONFIG_DIR/tunnel.json" <<EOF
{
"name": "$(json_escape "$TUNNEL_NAME")",
"id": "$(json_escape "$tunnel_id")",
"generated": "$(date -Iseconds)"
}
EOF
log_info "Wrote tunnel snapshot: $CONFIG_DIR/tunnel.json"
log_info "Next: ./scripts/20_dns_plan.sh"
}
main "$@"

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/_common.sh"
: "${ZONE_NAME:=}"
: "${HOSTNAME:=}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
main() {
[[ -n "$ZONE_NAME" ]] || die "ZONE_NAME is required."
[[ -n "$HOSTNAME" ]] || die "HOSTNAME is required."
[[ -f "$CONFIG_DIR/tunnel.json" ]] || log_warn "Missing tunnel snapshot (run 11_tunnel_apply.sh first)."
echo "[PLAN] $(date -Iseconds) DNS route plan"
echo "[PLAN] Ensure CNAME exists for hostname:"
echo " $HOSTNAME -> <tunnel-id>.cfargotunnel.com"
echo "[PLAN] Cloudflare API will be used to find zone id for: $ZONE_NAME"
echo "[PLAN] Next: ./scripts/21_dns_apply.sh (requires DRY_RUN=0)"
}
main "$@"

View File

@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/_common.sh"
: "${CF_API_TOKEN:=}"
: "${ZONE_NAME:=}"
: "${HOSTNAME:=}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
api() {
local method="$1"; shift
local url="$1"; shift
curl -sS -X "$method" "$url" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
"$@"
}
main() {
confirm_gate
[[ -n "$CF_API_TOKEN" ]] || die "CF_API_TOKEN is required."
[[ -n "$ZONE_NAME" ]] || die "ZONE_NAME is required."
[[ -n "$HOSTNAME" ]] || die "HOSTNAME is required."
[[ -f "$CONFIG_DIR/tunnel.json" ]] || die "Missing tunnel snapshot: $CONFIG_DIR/tunnel.json"
local tunnel_id; tunnel_id="$(jq -r '.id' "$CONFIG_DIR/tunnel.json")"
[[ -n "$tunnel_id" && "$tunnel_id" != "null" ]] || die "Invalid tunnel id in tunnel.json"
log_info "Resolving zone id for: $ZONE_NAME"
local z; z="$(api GET "https://api.cloudflare.com/client/v4/zones?name=$ZONE_NAME" | jq -r '.result[0].id' )"
[[ -n "$z" && "$z" != "null" ]] || die "Unable to resolve zone id for $ZONE_NAME"
local cname_target="${tunnel_id}.cfargotunnel.com"
log_info "Ensuring CNAME: $HOSTNAME -> $cname_target"
# Find existing record
local rec; rec="$(api GET "https://api.cloudflare.com/client/v4/zones/$z/dns_records?type=CNAME&name=$HOSTNAME")"
local rec_id; rec_id="$(echo "$rec" | jq -r '.result[0].id' )"
if [[ -n "$rec_id" && "$rec_id" != "null" ]]; then
log_info "Updating existing DNS record id: $rec_id"
api PUT "https://api.cloudflare.com/client/v4/zones/$z/dns_records/$rec_id" \
--data "{\"type\":\"CNAME\",\"name\":\"$HOSTNAME\",\"content\":\"$cname_target\",\"ttl\":1,\"proxied\":true}" \
| jq -e '.success==true' >/dev/null || die "Failed to update DNS record."
else
log_info "Creating new DNS record"
api POST "https://api.cloudflare.com/client/v4/zones/$z/dns_records" \
--data "{\"type\":\"CNAME\",\"name\":\"$HOSTNAME\",\"content\":\"$cname_target\",\"ttl\":1,\"proxied\":true}" \
| jq -e '.success==true' >/dev/null || die "Failed to create DNS record."
fi
# Save snapshot
cat > "$CONFIG_DIR/dns_route.json" <<EOF
{
"zone_name": "$(json_escape "$ZONE_NAME")",
"hostname": "$(json_escape "$HOSTNAME")",
"target": "$(json_escape "$cname_target")",
"generated": "$(date -Iseconds)"
}
EOF
log_info "Wrote DNS snapshot: $CONFIG_DIR/dns_route.json"
log_info "Next: ./scripts/30_service_plan.sh"
}
main "$@"

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/_common.sh"
: "${LOCAL_SERVICE:=}"
: "${SERVICE_NAME:=cloudflared-tunnel}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
main() {
[[ -n "$LOCAL_SERVICE" ]] || die "LOCAL_SERVICE is required."
[[ -f "$CONFIG_DIR/tunnel.json" ]] || log_warn "Missing tunnel snapshot (run 11_tunnel_apply.sh first)."
echo "[PLAN] $(date -Iseconds) Service plan"
echo "[PLAN] Generate config.yml under: $CONFIG_DIR/config.yml"
echo "[PLAN] Create systemd unit: /etc/systemd/system/$SERVICE_NAME.service"
echo "[PLAN] Unit will run: cloudflared tunnel --config $CONFIG_DIR/config.yml run"
echo "[PLAN] Ingress default: $LOCAL_SERVICE"
echo "[PLAN] Next: ./scripts/31_service_apply.sh (requires DRY_RUN=0)"
}
main "$@"

View File

@@ -0,0 +1,72 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/_common.sh"
: "${TUNNEL_NAME:=}"
: "${HOSTNAME:=}"
: "${LOCAL_SERVICE:=}"
: "${SERVICE_NAME:=cloudflared-tunnel}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
main() {
confirm_gate
need systemctl
[[ -n "$TUNNEL_NAME" ]] || die "TUNNEL_NAME is required."
[[ -n "$HOSTNAME" ]] || die "HOSTNAME is required."
[[ -n "$LOCAL_SERVICE" ]] || die "LOCAL_SERVICE is required."
[[ -f "$CONFIG_DIR/tunnel.json" ]] || die "Missing tunnel snapshot: $CONFIG_DIR/tunnel.json"
local tunnel_id; tunnel_id="$(jq -r '.id' "$CONFIG_DIR/tunnel.json")"
[[ -n "$tunnel_id" && "$tunnel_id" != "null" ]] || die "Invalid tunnel id in tunnel.json"
mkdir -p "$CONFIG_DIR"
# Generate cloudflared config
cat > "$CONFIG_DIR/config.yml" <<EOF
tunnel: $tunnel_id
credentials-file: $HOME/.cloudflared/$tunnel_id.json
ingress:
- hostname: $HOSTNAME
service: $LOCAL_SERVICE
- service: http_status:404
EOF
log_info "Wrote config: $CONFIG_DIR/config.yml"
log_warn "NOTE: credentials-file expects: $HOME/.cloudflared/$tunnel_id.json"
log_warn "If you created the tunnel on a different machine, copy that credentials file."
# Create systemd unit
local unit="/etc/systemd/system/$SERVICE_NAME.service"
sudo cp -a "$unit" "$unit.bak.$(date -Iseconds | tr ':' '-')" 2>/dev/null || true
sudo tee "$unit" >/dev/null <<EOF
[Unit]
Description=Cloudflare Tunnel ($TUNNEL_NAME)
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
Environment=HOME=$HOME
ExecStart=$(command -v cloudflared) tunnel --config $CONFIG_DIR/config.yml run
Restart=on-failure
RestartSec=3s
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload
sudo systemctl enable "$SERVICE_NAME"
sudo systemctl restart "$SERVICE_NAME"
sudo systemctl --no-pager status "$SERVICE_NAME" | head -n 30 || true
log_info "Service applied: $SERVICE_NAME"
log_info "Next: ./scripts/90_verify.sh"
}
main "$@"

View File

@@ -0,0 +1,54 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/_common.sh"
: "${ZONE_NAME:=}"
: "${HOSTNAME:=}"
: "${SERVICE_NAME:=cloudflared-tunnel}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
main() {
local status="$SKILL_ROOT/outputs/status_matrix.json"
local ok_tunnel=false ok_dns=false ok_config=false ok_service=false
if [[ -f "$CONFIG_DIR/tunnel.json" ]]; then ok_tunnel=true; fi
if [[ -f "$CONFIG_DIR/dns_route.json" ]]; then ok_dns=true; fi
if [[ -f "$CONFIG_DIR/config.yml" ]]; then ok_config=true; fi
if command -v systemctl >/dev/null 2>&1; then
if systemctl is-active "$SERVICE_NAME" >/dev/null 2>&1; then ok_service=true; fi
fi
blockers="[]"
if [[ "$ok_tunnel" != "true" ]]; then blockers='["tunnel_not_created"]'
elif [[ "$ok_dns" != "true" ]]; then blockers='["dns_route_missing"]'
elif [[ "$ok_config" != "true" ]]; then blockers='["config_missing"]'
fi
cat > "$status" <<EOF
{
"skill": "cloudflare-tunnel-manager",
"timestamp": "$(date -Iseconds)",
"checks": [
{"name":"tunnel_snapshot", "ok": $ok_tunnel},
{"name":"dns_snapshot", "ok": $ok_dns},
{"name":"config_present", "ok": $ok_config},
{"name":"service_active", "ok": $ok_service}
],
"blockers": $blockers,
"warnings": [],
"next_steps": [
"Confirm hostname routes to expected service",
"Record tunnel id + hostname in LAWCHAIN (optional)",
"Proceed to gitea-bootstrap or proof pipeline skills"
]
}
EOF
log_info "Wrote $status"
cat "$status"
}
main "$@"

View File

@@ -0,0 +1,82 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$SCRIPT_DIR")"
source "$SCRIPT_DIR/_common.sh"
: "${TUNNEL_NAME:=}"
: "${ZONE_NAME:=}"
: "${HOSTNAME:=}"
: "${LOCAL_SERVICE:=}"
: "${SERVICE_NAME:=cloudflared-tunnel}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
main() {
mkdir -p "$SKILL_ROOT/outputs"
local report="$SKILL_ROOT/outputs/audit_report.md"
local status="$SKILL_ROOT/outputs/status_matrix.json"
local tunnel_id="(unknown)"
[[ -f "$CONFIG_DIR/tunnel.json" ]] && tunnel_id="$(jq -r '.id' "$CONFIG_DIR/tunnel.json")"
cat > "$report" <<EOF
# Cloudflare Tunnel Audit Report
**Generated:** $(date -Iseconds)
**Tunnel Name:** $(json_escape "${TUNNEL_NAME:-}")
**Tunnel ID:** $(json_escape "$tunnel_id")
**Hostname:** $(json_escape "${HOSTNAME:-}")
**Zone:** $(json_escape "${ZONE_NAME:-}")
**Local Service:** $(json_escape "${LOCAL_SERVICE:-}")
**Service Unit:** $(json_escape "$SERVICE_NAME")
**Skill Version:** 1.0.0
---
## Artifacts
| Item | Path |
|---|---|
| Tunnel Snapshot | \`$CONFIG_DIR/tunnel.json\` |
| DNS Snapshot | \`$CONFIG_DIR/dns_route.json\` |
| cloudflared Config | \`$CONFIG_DIR/config.yml\` |
| Status Matrix | \`$SKILL_ROOT/outputs/status_matrix.json\` |
---
## Status Matrix
$(if [[ -f "$status" ]]; then
echo '```json'
cat "$status"
echo '```'
else
echo "_Missing status_matrix.json — run 90_verify.sh first._"
fi)
---
## EU Compliance Declaration
| Aspect | Value |
|---|---|
| Data Residency | EU (Ireland - Dublin) |
| Jurisdiction | Irish Law |
| DNS Provider | Cloudflare |
| Tunnel | Encrypted transport |
---
## Rollback
- Undo service: \`./scripts/rollback/undo_service.sh\`
- Undo DNS: \`./scripts/rollback/undo_dns.sh\`
- Undo tunnel (delete): \`./scripts/rollback/undo_tunnel.sh\`
EOF
log_info "Wrote $report"
cat "$report"
}
main "$@"

View File

@@ -0,0 +1,41 @@
#!/usr/bin/env bash
set -euo pipefail
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; }
need(){ command -v "$1" >/dev/null 2>&1 || die "Missing required tool: $1"; }
json_escape() {
local s="$1"
s="${s//\\/\\\\}"
s="${s//\"/\\\"}"
s="${s//$'\n'/\\n}"
s="${s//$'\r'/\\r}"
s="${s//$'\t'/\\t}"
printf "%s" "$s"
}
confirm_gate() {
: "${DRY_RUN:=1}"
: "${REQUIRE_CONFIRM:=1}"
: "${CONFIRM_PHRASE:=I UNDERSTAND THIS CAN CHANGE DNS AND TUNNEL ROUTES}"
[[ "$DRY_RUN" == "0" ]] || die "DRY_RUN=$DRY_RUN (set DRY_RUN=0 to apply)."
if [[ "$REQUIRE_CONFIRM" == "1" ]]; then
echo "Type to confirm:"
echo " $CONFIRM_PHRASE"
read -r input
[[ "$input" == "$CONFIRM_PHRASE" ]] || die "Confirmation phrase mismatch."
fi
}
# Minimal wrapper: prefer explicit token env var over stored login
cf_env_check() {
: "${CF_API_TOKEN:=}"
: "${CF_ACCOUNT_ID:=}"
[[ -n "$CF_API_TOKEN" ]] || die "CF_API_TOKEN is required."
[[ -n "$CF_ACCOUNT_ID" ]] || die "CF_ACCOUNT_ID is required."
}

View File

@@ -0,0 +1,44 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
source "$SKILL_ROOT/scripts/_common.sh"
: "${CF_API_TOKEN:=}"
: "${ZONE_NAME:=}"
: "${HOSTNAME:=}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
api() {
local method="$1"; shift
local url="$1"; shift
curl -sS -X "$method" "$url" \
-H "Authorization: Bearer $CF_API_TOKEN" \
-H "Content-Type: application/json" \
"$@"
}
main() {
confirm_gate
[[ -n "$CF_API_TOKEN" ]] || die "CF_API_TOKEN is required."
[[ -n "$ZONE_NAME" ]] || die "ZONE_NAME is required."
[[ -n "$HOSTNAME" ]] || die "HOSTNAME is required."
need jq
need curl
local z; z="$(api GET "https://api.cloudflare.com/client/v4/zones?name=$ZONE_NAME" | jq -r '.result[0].id')"
[[ -n "$z" && "$z" != "null" ]] || die "Unable to resolve zone id for $ZONE_NAME"
local rec; rec="$(api GET "https://api.cloudflare.com/client/v4/zones/$z/dns_records?type=CNAME&name=$HOSTNAME")"
local rec_id; rec_id="$(echo "$rec" | jq -r '.result[0].id')"
if [[ -n "$rec_id" && "$rec_id" != "null" ]]; then
log_warn "Deleting DNS record id: $rec_id ($HOSTNAME)"
api DELETE "https://api.cloudflare.com/client/v4/zones/$z/dns_records/$rec_id" | jq -e '.success==true' >/dev/null || die "Failed to delete DNS record."
else
log_warn "No DNS record found for $HOSTNAME"
fi
rm -f "$CONFIG_DIR/dns_route.json" || true
log_info "DNS rollback complete."
}
main "$@"

View File

@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
source "$SKILL_ROOT/scripts/_common.sh"
: "${SERVICE_NAME:=cloudflared-tunnel}"
main() {
confirm_gate
need systemctl
log_warn "Stopping/disabling service: $SERVICE_NAME"
sudo systemctl stop "$SERVICE_NAME" || true
sudo systemctl disable "$SERVICE_NAME" || true
sudo systemctl daemon-reload || true
log_info "Service rollback complete."
}
main "$@"

View File

@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
SKILL_ROOT="$(dirname "$(dirname "$SCRIPT_DIR")")"
source "$SKILL_ROOT/scripts/_common.sh"
: "${TUNNEL_NAME:=}"
: "${CONFIG_DIR:=$SKILL_ROOT/outputs/config}"
main() {
confirm_gate
[[ -n "$TUNNEL_NAME" ]] || die "TUNNEL_NAME is required."
if [[ -f "$CONFIG_DIR/tunnel.json" ]]; then
local tunnel_id; tunnel_id="$(jq -r '.id' "$CONFIG_DIR/tunnel.json")"
if [[ -n "$tunnel_id" && "$tunnel_id" != "null" ]]; then
log_warn "Deleting tunnel via cloudflared: $TUNNEL_NAME ($tunnel_id)"
cloudflared tunnel delete -f "$tunnel_id" || cloudflared tunnel delete -f "$TUNNEL_NAME" || true
fi
else
log_warn "No tunnel.json snapshot; attempting delete by name: $TUNNEL_NAME"
cloudflared tunnel delete -f "$TUNNEL_NAME" || true
fi
rm -f "$CONFIG_DIR/tunnel.json" "$CONFIG_DIR/config.yml" || true
log_info "Tunnel rollback complete."
}
main "$@"