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:
35
cloudflare-tunnel-manager/scripts/00_preflight.sh
Normal file
35
cloudflare-tunnel-manager/scripts/00_preflight.sh
Normal 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 "$@"
|
||||
22
cloudflare-tunnel-manager/scripts/10_tunnel_plan.sh
Normal file
22
cloudflare-tunnel-manager/scripts/10_tunnel_plan.sh
Normal 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 "$@"
|
||||
55
cloudflare-tunnel-manager/scripts/11_tunnel_apply.sh
Normal file
55
cloudflare-tunnel-manager/scripts/11_tunnel_apply.sh
Normal 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 "$@"
|
||||
23
cloudflare-tunnel-manager/scripts/20_dns_plan.sh
Normal file
23
cloudflare-tunnel-manager/scripts/20_dns_plan.sh
Normal 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 "$@"
|
||||
68
cloudflare-tunnel-manager/scripts/21_dns_apply.sh
Normal file
68
cloudflare-tunnel-manager/scripts/21_dns_apply.sh
Normal 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 "$@"
|
||||
23
cloudflare-tunnel-manager/scripts/30_service_plan.sh
Normal file
23
cloudflare-tunnel-manager/scripts/30_service_plan.sh
Normal 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 "$@"
|
||||
72
cloudflare-tunnel-manager/scripts/31_service_apply.sh
Normal file
72
cloudflare-tunnel-manager/scripts/31_service_apply.sh
Normal 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 "$@"
|
||||
54
cloudflare-tunnel-manager/scripts/90_verify.sh
Normal file
54
cloudflare-tunnel-manager/scripts/90_verify.sh
Normal 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 "$@"
|
||||
82
cloudflare-tunnel-manager/scripts/99_report.sh
Normal file
82
cloudflare-tunnel-manager/scripts/99_report.sh
Normal 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 "$@"
|
||||
41
cloudflare-tunnel-manager/scripts/_common.sh
Normal file
41
cloudflare-tunnel-manager/scripts/_common.sh
Normal 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."
|
||||
}
|
||||
44
cloudflare-tunnel-manager/scripts/rollback/undo_dns.sh
Normal file
44
cloudflare-tunnel-manager/scripts/rollback/undo_dns.sh
Normal 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 "$@"
|
||||
18
cloudflare-tunnel-manager/scripts/rollback/undo_service.sh
Normal file
18
cloudflare-tunnel-manager/scripts/rollback/undo_service.sh
Normal 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 "$@"
|
||||
28
cloudflare-tunnel-manager/scripts/rollback/undo_tunnel.sh
Normal file
28
cloudflare-tunnel-manager/scripts/rollback/undo_tunnel.sh
Normal 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 "$@"
|
||||
Reference in New Issue
Block a user