v2: ID verification by state, 15 photos/5 albums, 20 quick replies, logo, mobile polish
This commit is contained in:
53
deploy/_cf_api_probe.py
Normal file
53
deploy/_cf_api_probe.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
paths = [
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel",
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}",
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations",
|
||||
f"/accounts/{ACCOUNT}/tunnels",
|
||||
f"/accounts/{ACCOUNT}/tunnels/{TUNNEL}",
|
||||
f"/accounts/{ACCOUNT}/tunnels/{TUNNEL}/configurations",
|
||||
f"/accounts/{ACCOUNT}/access/apps",
|
||||
f"/accounts/{ACCOUNT}/access/apps/policies",
|
||||
f"/zones/b9e032992b315331c8b0571473fedc89/dns_records?name=exposedgays.com",
|
||||
f"/accounts/{ACCOUNT}/teamnet/routes",
|
||||
f"/accounts/{ACCOUNT}/teamnet/hostnames",
|
||||
f"/accounts/{ACCOUNT}/teamnet/tunnels",
|
||||
f"/accounts/{ACCOUNT}/teamnet/tunnels/{TUNNEL}/routes",
|
||||
]
|
||||
|
||||
for path in paths:
|
||||
req = urllib.request.Request(CF + path, headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=25) as r:
|
||||
data = json.load(r)
|
||||
result = data.get("result")
|
||||
summary = ""
|
||||
if isinstance(result, list):
|
||||
summary = f"list[{len(result)}]"
|
||||
if result and isinstance(result[0], dict):
|
||||
summary += " keys=" + ",".join(list(result[0].keys())[:6])
|
||||
elif isinstance(result, dict):
|
||||
summary = "dict keys=" + ",".join(list(result.keys())[:8])
|
||||
print(f"OK {path} -> {summary}")
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode()[:120]
|
||||
print(f"ERR {e.code} {path} -> {body}")
|
||||
38
deploy/_cf_dns_both.py
Normal file
38
deploy/_cf_dns_both.py
Normal file
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
for domain in ["exposedgays.com", "thepinkpulse.com"]:
|
||||
req = urllib.request.Request(
|
||||
f"{CF}/zones?name={domain}",
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
zones = json.load(r)["result"]
|
||||
if not zones:
|
||||
print(domain, "NO ZONE")
|
||||
continue
|
||||
zid = zones[0]["id"]
|
||||
req2 = urllib.request.Request(
|
||||
f"{CF}/zones/{zid}/dns_records?per_page=100",
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req2, timeout=30) as r:
|
||||
recs = json.load(r)["result"]
|
||||
print(f"\n=== {domain} zone {zid} ===")
|
||||
for rec in recs:
|
||||
if rec["type"] in ("A", "CNAME", "AAAA") and "domainconnect" not in rec["name"]:
|
||||
print(f" {rec['type']:5} {rec['name']:35} -> {rec['content']} proxied={rec.get('proxied')}")
|
||||
26
deploy/_check_supabase_proxy.py
Normal file
26
deploy/_check_supabase_proxy.py
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
for host, user in [("10.10.0.11", "root"), ("10.10.0.10", "localadministrator")]:
|
||||
print(f"\n######## {host} ########")
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
try:
|
||||
c.connect(host, username=user, password=PASS, timeout=20)
|
||||
except Exception as ex:
|
||||
print("connect failed", ex)
|
||||
continue
|
||||
cmds = [
|
||||
"ss -tlnp | grep -E ':80 |:443 ' || true",
|
||||
"curl -sSI --max-time 10 -H 'Host: thepinkpulse.com' http://127.0.0.1/ | head -6",
|
||||
"curl -sSI --max-time 10 -H 'Host: exposedgays.com' http://127.0.0.1/ | head -6",
|
||||
"curl -sSI --max-time 10 -H 'Host: avbeat.com' http://127.0.0.1/ | head -6",
|
||||
"curl -sSI --max-time 10 -H 'Host: exposedgays.onsethost.com' http://127.0.0.1/ | head -6",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:1500])
|
||||
c.close()
|
||||
44
deploy/_check_www_dns.py
Normal file
44
deploy/_check_www_dns.py
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ZID = "b9e032992b315331c8b0571473fedc89"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
req = urllib.request.Request(
|
||||
CF + f"/zones/{ZID}/dns_records?per_page=100",
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
recs = json.load(r)["result"]
|
||||
|
||||
print("All exposedgays apex/www records:")
|
||||
for rec in recs:
|
||||
if rec["name"] in ("exposedgays.com", "www.exposedgays.com"):
|
||||
print(f" id={rec['id']} {rec['type']} {rec['name']} -> {rec['content']} proxied={rec.get('proxied')}")
|
||||
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for cmd in [
|
||||
"dig @1.1.1.1 www.exposedgays.com A +short",
|
||||
"dig @1.1.1.1 www.exposedgays.com CNAME +short",
|
||||
"dig @annabel.ns.cloudflare.com www.exposedgays.com A +short",
|
||||
"curl -sSI --max-time 15 https://www.exposedgays.com/ | head -6",
|
||||
]:
|
||||
print(f"\n$ {cmd}")
|
||||
_, o, e = ssh.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode())
|
||||
ssh.close()
|
||||
11
deploy/_cloudflared_subcmds.py
Normal file
11
deploy/_cloudflared_subcmds.py
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
_, o, e = c.exec_command("cloudflared tunnel --help 2>&1", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode())
|
||||
c.close()
|
||||
24
deploy/_compare_avbeat.py
Normal file
24
deploy/_compare_avbeat.py
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"dig @1.1.1.1 avbeat.com CNAME +short",
|
||||
"dig @1.1.1.1 exposedgays.com CNAME +short",
|
||||
"dig @1.1.1.1 avbeat.com A +short",
|
||||
"dig @1.1.1.1 exposedgays.com A +short",
|
||||
"curl -sSI --max-time 15 https://avbeat.com/ | head -8",
|
||||
"curl -sSI --max-time 15 https://exposedgays.com/ | head -8",
|
||||
"curl -sSI --max-time 15 -k -H 'Host: avbeat.com' https://127.0.0.1/ | head -8",
|
||||
"curl -sSI --max-time 15 -k -H 'Host: exposedgays.com' https://127.0.0.1/ | head -8",
|
||||
"ps aux | grep cloudflared | grep -v grep",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=40)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:2000])
|
||||
c.close()
|
||||
19
deploy/_coolify_cf_hunt.py
Normal file
19
deploy/_coolify_cf_hunt.py
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
f"echo '{PASS}' | sudo -S grep -rniE 'cfut_|GLOBAL_API_KEY|tunnel.*token|CLOUDFLARE' /data/coolify /home/localadministrator/.coolify-token /home/localadministrator/.config 2>/dev/null | head -40",
|
||||
"cat /home/localadministrator/.coolify-token 2>/dev/null | wc -c",
|
||||
"docker exec coolify-db psql -U coolify -d coolify -c \"SELECT key,left(value,20) FROM environment_variables WHERE key ILIKE '%cloudflare%' OR key ILIKE '%tunnel%' OR value LIKE 'cfut_%' LIMIT 20;\"",
|
||||
"sudo cat /etc/cloudflared/config.yml | head -40",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd[:110]} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=120)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:5000])
|
||||
c.close()
|
||||
47
deploy/_diag_now.py
Normal file
47
deploy/_diag_now.py
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
f"echo '{PASS}' | sudo -S grep -rho 'cfut_[A-Za-z0-9_-]{{20,}}' /etc/infra /etc/cloudflared /root /home/localadministrator 2>/dev/null | sort -u",
|
||||
f"echo '{PASS}' | sudo -S grep -rniE 'GLOBAL_API_KEY|X-Auth-Key|CLOUDFLARE_EMAIL' /etc/infra /etc/cloudflared /root /home/localadministrator 2>/dev/null | head -30",
|
||||
"sudo cat /etc/cloudflared/config.yml",
|
||||
"sudo journalctl -u cloudflared --no-pager -n 3 | grep 'Updated to new configuration' | tail -1 | python3 -c \"import sys,re; t=sys.stdin.read(); print('ingress_count', len(re.findall(r'hostname', t))); print('exposedgays', 'exposedgays' in t); print('thepinkpulse', 'thepinkpulse' in t)\"",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd[:100]} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=120)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:15000])
|
||||
c.close()
|
||||
|
||||
tokens = set()
|
||||
for label, tok in [
|
||||
("zlUt", "cfut_zlUt5lAKVsu7qIcRCHJnyhAvpJVF5jx24HlrIEn9a8fcd6a1"),
|
||||
("IOm1", "cfut_IOm1DJW4pDkPCuQA6UoOjqO6Tcv2zU10y2PSw82m379f7db3f7"),
|
||||
("vF51", "cfut_vF51avCWJV9EnCM4lXS2v60jXo6TPQZ0yxESHuRn5b0f3515"),
|
||||
]:
|
||||
req = urllib.request.Request(
|
||||
f"{CF}/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations",
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
data = json.load(r)
|
||||
ingress = data.get("result", {}).get("config", {}).get("ingress", [])
|
||||
eg = [i.get("hostname") for i in ingress if i.get("hostname") and "exposedgays" in i.get("hostname", "")]
|
||||
print(f"\n[{label}] tunnel GET OK count={len(ingress)} exposedgays={eg}")
|
||||
except urllib.error.HTTPError as e:
|
||||
err = json.loads(e.read().decode())
|
||||
print(f"\n[{label}] tunnel GET {e.code} {err.get('errors')}")
|
||||
23
deploy/_dns_compare.py
Normal file
23
deploy/_dns_compare.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"dig @1.1.1.1 thepinkpulse.com ANY +noall +answer",
|
||||
"dig @1.1.1.1 exposedgays.com ANY +noall +answer",
|
||||
"dig @1.1.1.1 www.thepinkpulse.com CNAME +short",
|
||||
"dig @1.1.1.1 www.exposedgays.com CNAME +short",
|
||||
"curl -sSL --max-time 15 https://thepinkpulse.com/ | head -c 150",
|
||||
"curl -sSL --max-time 15 https://exposedgays.com/ 2>&1 | head -c 150",
|
||||
"curl -sSL --max-time 15 -k -H 'Host: thepinkpulse.com' https://127.0.0.1/ | head -c 150",
|
||||
"curl -sSL --max-time 15 -k -H 'Host: exposedgays.com' https://127.0.0.1/ | head -c 150",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=40)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:2000])
|
||||
c.close()
|
||||
35
deploy/_dump_remote.py
Normal file
35
deploy/_dump_remote.py
Normal file
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
_, o, e = c.exec_command(
|
||||
"sudo journalctl -u cloudflared --no-pager -n 200 | grep 'Updated to new configuration' | tail -1",
|
||||
timeout=30,
|
||||
)
|
||||
o.channel.recv_exit_status()
|
||||
line = (o.read() + e.read()).decode()
|
||||
start = line.find('config="')
|
||||
if start == -1:
|
||||
print("missing config=")
|
||||
print(line[:500])
|
||||
c.close()
|
||||
raise SystemExit(1)
|
||||
raw = line[start + len('config="'):]
|
||||
# config value ends before final quote on line
|
||||
raw = raw.rsplit('"', 1)[0]
|
||||
raw = raw.encode().decode("unicode_escape")
|
||||
cfg = json.loads(raw)
|
||||
ingress = cfg.get("ingress", [])
|
||||
print(f"total rules: {len(ingress)}")
|
||||
for i in ingress:
|
||||
h = i.get("hostname", "(catch-all)")
|
||||
svc = i.get("service")
|
||||
print(f" {h} -> {svc}")
|
||||
if h and "exposed" in h:
|
||||
print("FOUND")
|
||||
c.close()
|
||||
21
deploy/_find_cert_and_metrics.py
Normal file
21
deploy/_find_cert_and_metrics.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
f"echo '{PASS}' | sudo -S find /etc/cloudflared /home/localadministrator/.cloudflared /root -name 'cert.pem' 2>/dev/null",
|
||||
"cloudflared tunnel --help 2>&1 | grep -i configuration",
|
||||
"cloudflared tunnel configuration --help 2>&1",
|
||||
"curl -s http://127.0.0.1:20241/metrics 2>/dev/null | grep -i ingress | head -10",
|
||||
"curl -s http://127.0.0.1:20242/metrics 2>/dev/null | head -5",
|
||||
"sudo journalctl -u cloudflared --no-pager -n 30 | grep -iE 'metrics|ingress|exposedgays|configuration'",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd[:100]} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=40)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:4000])
|
||||
c.close()
|
||||
23
deploy/_find_creds.py
Normal file
23
deploy/_find_creds.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"ls -la /etc/cloudflared/",
|
||||
"ls -la /home/localadministrator/.cloudflared/ 2>/dev/null || echo no_home_cloudflared",
|
||||
"ls -la /root/.cloudflared/ 2>/dev/null || echo no_root_cloudflared",
|
||||
"grep -n 'exposedgays\\|thepinkpulse\\|avbeat\\|catch' /etc/cloudflared/config.yml | tail -30",
|
||||
"sudo journalctl -u cloudflared --no-pager -n 2 | tail -2",
|
||||
"docker exec coolify-db psql -U coolify -d coolify -t -A -c \"SELECT uuid,name,status,fqdn FROM applications WHERE name='exposedgays';\"",
|
||||
"docker ps --format '{{.Names}} {{.Status}}' | grep ira4 || true",
|
||||
"curl -sS -o /dev/null -w 'local:%{http_code}' -k -H 'Host: exposedgays.com' https://127.0.0.1/",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=60)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:4000])
|
||||
c.close()
|
||||
131
deploy/_fix_tunnel_put.py
Normal file
131
deploy/_fix_tunnel_put.py
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add exposedgays.com to Cloudflare tunnel remote ingress using best available token."""
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
EG_RULES = [
|
||||
{
|
||||
"hostname": "exposedgays.com",
|
||||
"service": "http://10.10.0.11:80",
|
||||
"originRequest": {"httpHostHeader": "exposedgays.com"},
|
||||
},
|
||||
{
|
||||
"hostname": "www.exposedgays.com",
|
||||
"service": "http://10.10.0.11:80",
|
||||
"originRequest": {"httpHostHeader": "www.exposedgays.com"},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_pfsense_token():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
|
||||
|
||||
def cf(token, path, method="GET", data=None):
|
||||
req = urllib.request.Request(
|
||||
f"{CF}{path}",
|
||||
data=json.dumps(data).encode() if data is not None else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
return r.status, json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode())
|
||||
|
||||
|
||||
def ssh(cmd, timeout=60):
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
_, o, e = c.exec_command(cmd, timeout=timeout)
|
||||
o.channel.recv_exit_status()
|
||||
out = (o.read() + e.read()).decode(errors="replace")
|
||||
c.close()
|
||||
return out
|
||||
|
||||
|
||||
def get_remote_ingress_from_journal():
|
||||
raw = ssh(
|
||||
"sudo journalctl -u cloudflared --no-pager -n 200 | grep 'Updated to new configuration' | tail -1"
|
||||
)
|
||||
start = raw.find('config="')
|
||||
if start == -1:
|
||||
raise RuntimeError("no remote config in journal")
|
||||
payload = raw[start + len('config="'):].rsplit('"', 1)[0]
|
||||
payload = payload.encode().decode("unicode_escape")
|
||||
return json.loads(payload).get("ingress", [])
|
||||
|
||||
|
||||
def main():
|
||||
token = get_pfsense_token()
|
||||
code, verify = cf(token, "/user/tokens/verify")
|
||||
print(f"token verify: {code} {verify.get('success')}")
|
||||
|
||||
code, body = cf(token, f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations")
|
||||
print(f"API GET config: {code} {body.get('errors')}")
|
||||
|
||||
if body.get("success"):
|
||||
ingress = body["result"]["config"]["ingress"]
|
||||
source = "api"
|
||||
else:
|
||||
ingress = get_remote_ingress_from_journal()
|
||||
source = "journal"
|
||||
print(f"ingress source={source} rules={len(ingress)}")
|
||||
|
||||
hostnames = {i.get("hostname") for i in ingress if i.get("hostname")}
|
||||
changed = False
|
||||
for rule in EG_RULES:
|
||||
if rule["hostname"] not in hostnames:
|
||||
catch = next((i for i, x in enumerate(ingress) if not x.get("hostname")), len(ingress))
|
||||
ingress.insert(catch, rule)
|
||||
hostnames.add(rule["hostname"])
|
||||
changed = True
|
||||
print("will add", rule["hostname"])
|
||||
|
||||
if not changed:
|
||||
print("already present in ingress list")
|
||||
elif source != "api":
|
||||
print("BLOCKED: cannot PUT without API token — ingress list prepared but not pushed")
|
||||
print("Prepared rules:")
|
||||
for rule in EG_RULES:
|
||||
print(" ", rule)
|
||||
return 1
|
||||
|
||||
put_code, put = cf(
|
||||
token,
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations",
|
||||
"PUT",
|
||||
{"config": {"ingress": ingress, "warp-routing": {"enabled": False}}},
|
||||
)
|
||||
print(f"PUT: {put_code} success={put.get('success')} errors={put.get('errors')}")
|
||||
if not put.get("success"):
|
||||
return 1
|
||||
|
||||
print(ssh(f"echo '{PASS}' | sudo -S systemctl restart cloudflared"))
|
||||
time.sleep(10)
|
||||
print(ssh(
|
||||
"curl -sSI --max-time 20 https://exposedgays.com/ | head -12; "
|
||||
"curl -sSL --max-time 20 https://exposedgays.com/ | head -c 250"
|
||||
))
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
23
deploy/_investigate_tpp.py
Normal file
23
deploy/_investigate_tpp.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"dig @1.1.1.1 thepinkpulse.com CNAME +short",
|
||||
"dig @1.1.1.1 thepinkpulse.com A +short",
|
||||
"curl -sSI --max-time 15 https://thepinkpulse.com/ | head -12",
|
||||
"cloudflared tunnel run --help 2>&1 | grep -i remote",
|
||||
"cloudflared help 2>&1 | grep -i 'local\\|remote\\|propagat' | head -20",
|
||||
"grep -n 'exposedgays' /etc/cloudflared/config.yml",
|
||||
"wc -l /etc/cloudflared/config.yml",
|
||||
"cat /data/coolify/proxy/dynamic/onsethost-subdomains.yaml | head -60",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=40)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:5000])
|
||||
c.close()
|
||||
19
deploy/_mail_proxy_conf.py
Normal file
19
deploy/_mail_proxy_conf.py
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.11", username="localadministrator", password=PASS, timeout=20)
|
||||
cmds = [
|
||||
"cat /home/localadministrator/mail-proxy/default.conf",
|
||||
"cat /home/localadministrator/mail-proxy/start.sh",
|
||||
"curl -sSI --max-time 10 -H 'Host: coolify.onsethost.com' http://127.0.0.1/ | head -8",
|
||||
"curl -sSL --max-time 10 -H 'Host: coolify.onsethost.com' http://127.0.0.1/ | head -c 200",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:6000])
|
||||
c.close()
|
||||
18
deploy/_nginx_full.py
Normal file
18
deploy/_nginx_full.py
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.11", username="localadministrator", password=PASS, timeout=20)
|
||||
cmds = [
|
||||
"docker exec mail-onsethost-proxy nginx -T 2>/dev/null | head -120",
|
||||
"curl -k -sSL --max-time 10 https://10.10.0.11:80 -H 'Host: thepinkpulse.com' | head -c 300",
|
||||
"curl -k -sSL --max-time 10 https://10.10.0.11:80 -H 'Host: exposedgays.com' | head -c 300",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd[:90]} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:4000])
|
||||
c.close()
|
||||
25
deploy/_port80_supabase.py
Normal file
25
deploy/_port80_supabase.py
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
for src, host, user in [
|
||||
("supabase", "10.10.0.11", "localadministrator"),
|
||||
("coolify", "10.10.0.10", "localadministrator"),
|
||||
]:
|
||||
print(f"\n######## {src} ########")
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(host, username=user, password=PASS, timeout=20)
|
||||
cmds = [
|
||||
"sudo ss -tlnp | grep ':80 '",
|
||||
"docker ps --format 'table {{.Names}}\t{{.Ports}}' | grep -E '80|443|kong|proxy|nginx|traefik' ",
|
||||
"curl -sSI --max-time 10 -H 'Host: thepinkpulse.com' http://10.10.0.11/ | head -6",
|
||||
"curl -sSI --max-time 10 -H 'Host: thepinkpulse.com' http://127.0.0.1/ | head -6",
|
||||
"curl -sSI --max-time 10 -H 'Host: exposedgays.com' -k https://127.0.0.1/ | head -6",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:2000])
|
||||
c.close()
|
||||
41
deploy/_probe_worker.py
Normal file
41
deploy/_probe_worker.py
Normal file
@@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ZID = "b9e032992b315331c8b0571473fedc89" # exposedgays zone
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
|
||||
def call(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return r.status, json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode())
|
||||
|
||||
|
||||
for path in [
|
||||
f"/zones/{ZID}/workers/routes",
|
||||
f"/accounts/2599c23bbb1255dbb73e8d34b4115fda/workers/scripts",
|
||||
f"/zones/{ZID}/rulesets/phases/http_request_dynamic_redirect/entrypoint",
|
||||
]:
|
||||
code, res = call(path)
|
||||
print(path, code, res.get("success"), (res.get("errors") or res.get("result"))[:2] if isinstance(res.get("result"), list) else res.get("errors"))
|
||||
26
deploy/_read_onsethost_env.py
Normal file
26
deploy/_read_onsethost_env.py
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
paths = [
|
||||
"/home/localadministrator/coder/onsethost/.env.local",
|
||||
"/data/coolify/applications/shuurwefh5ipw3qfvmbq4vic/.env",
|
||||
]
|
||||
for p in paths:
|
||||
print(f"\n=== {p} ===")
|
||||
_, o, e = c.exec_command(f"grep -i cloudflare {p} 2>/dev/null || echo missing", timeout=20)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
for line in text.splitlines():
|
||||
if "=" in line:
|
||||
k, _, v = line.partition("=")
|
||||
v = v.strip().strip('"')
|
||||
if len(v) > 16:
|
||||
v = v[:12] + "..." + v[-4:]
|
||||
print(f"{k}={v}")
|
||||
else:
|
||||
print(line)
|
||||
c.close()
|
||||
21
deploy/_read_pfsense_env.py
Normal file
21
deploy/_read_pfsense_env.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
for line in text.splitlines():
|
||||
if any(k in line.upper() for k in ["CLOUDFLARE", "CF_", "TUNNEL", "AUTH"]):
|
||||
if "SECRET" in line.upper() or "TOKEN" in line.upper() or "KEY" in line.upper() or "EMAIL" in line.upper():
|
||||
k, _, v = line.partition("=")
|
||||
v = v.strip()
|
||||
if len(v) > 12:
|
||||
v = v[:8] + "..." + v[-4:]
|
||||
print(f"{k}={v}")
|
||||
else:
|
||||
print(line)
|
||||
40
deploy/_remote_ingress.py
Normal file
40
deploy/_remote_ingress.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Parse cloudflared journal for remote ingress hostnames."""
|
||||
import json
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
_, o, e = c.exec_command(
|
||||
"sudo journalctl -u cloudflared --no-pager -n 100 | grep 'Updated to new configuration' | tail -1",
|
||||
timeout=30,
|
||||
)
|
||||
o.channel.recv_exit_status()
|
||||
line = (o.read() + e.read()).decode()
|
||||
idx = line.find('version=')
|
||||
if idx == -1:
|
||||
print("no config line found")
|
||||
print(line[:500])
|
||||
c.close()
|
||||
raise SystemExit(1)
|
||||
# extract JSON after version=N
|
||||
import re
|
||||
m = re.search(r"config=(\{.*\})", line)
|
||||
if not m:
|
||||
print("no json in line")
|
||||
print(line[:1000])
|
||||
c.close()
|
||||
raise SystemExit(1)
|
||||
cfg = json.loads(m.group(1))
|
||||
ingress = cfg.get("ingress", [])
|
||||
hosts = sorted(h for h in (i.get("hostname") for i in ingress) if h)
|
||||
print(f"remote ingress hostnames: {len(hosts)}")
|
||||
for h in hosts:
|
||||
if any(x in (h or "") for x in ["exposed", "avbeat", "pink", "freerange", "onsethost"]):
|
||||
print(" ", h)
|
||||
print("...")
|
||||
for target in ["exposedgays.com", "www.exposedgays.com", "avbeat.com", "thepinkpulse.com", "*.onsethost.com"]:
|
||||
print(f"{target}: {target in hosts}")
|
||||
c.close()
|
||||
89
deploy/_scan_tokens_tunnel.py
Normal file
89
deploy/_scan_tokens_tunnel.py
Normal file
@@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
tokens = set()
|
||||
for host, user, sudo in [
|
||||
("10.10.0.10", "localadministrator", True),
|
||||
("10.10.0.1", "admin", False),
|
||||
]:
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(host, username=user, password=PASS, timeout=15)
|
||||
if sudo:
|
||||
cmd = (
|
||||
f"echo '{PASS}' | sudo -S bash -c \""
|
||||
"grep -rhoE 'cfut_[A-Za-z0-9_-]{20,}' "
|
||||
"/etc/infra /etc/cloudflared /root /home/localadministrator "
|
||||
"/data/coolify 2>/dev/null | sort -u\""
|
||||
)
|
||||
else:
|
||||
cmd = "grep -oE 'cfut_[A-Za-z0-9_-]{20,}' /usr/local/etc/wan-dns-failover.env /usr/local/etc/*.env 2>/dev/null"
|
||||
_, o, e = c.exec_command(cmd, timeout=180)
|
||||
o.channel.recv_exit_status()
|
||||
for line in (o.read() + e.read()).decode().splitlines():
|
||||
if line.startswith("cfut_"):
|
||||
tokens.add(line.strip())
|
||||
c.close()
|
||||
|
||||
# local workspace tokens
|
||||
for t in [
|
||||
"cfut_vF51avCWJV9EnCM4lXS2v60jXo6TPQZ0yxESHuRn5b0f3515",
|
||||
"cfut_IOm1DJW4pDkPCuQA6UoOjqO6Tcv2zU10y2PSw82m379f7db3f7",
|
||||
]:
|
||||
tokens.add(t)
|
||||
|
||||
print(f"probing {len(tokens)} tokens for tunnel PUT...")
|
||||
|
||||
EG = [
|
||||
{"hostname": "exposedgays.com", "service": "https://localhost:443", "originRequest": {"noTLSVerify": True}},
|
||||
{"hostname": "www.exposedgays.com", "service": "https://localhost:443", "originRequest": {"noTLSVerify": True}},
|
||||
]
|
||||
|
||||
for tok in sorted(tokens):
|
||||
req = urllib.request.Request(
|
||||
f"{CF}/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations",
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
data = json.load(r)
|
||||
ingress = data["result"]["config"]["ingress"]
|
||||
hostnames = {i.get("hostname") for i in ingress if i.get("hostname")}
|
||||
changed = False
|
||||
for rule in EG:
|
||||
if rule["hostname"] not in hostnames:
|
||||
catch = next((i for i, x in enumerate(ingress) if not x.get("hostname")), len(ingress))
|
||||
ingress.insert(catch, rule)
|
||||
changed = True
|
||||
if changed:
|
||||
put_req = urllib.request.Request(
|
||||
f"{CF}/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations",
|
||||
data=json.dumps({"config": {"ingress": ingress, "warp-routing": {"enabled": False}}}).encode(),
|
||||
method="PUT",
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(put_req, timeout=60) as pr:
|
||||
put = json.load(pr)
|
||||
print(f"SUCCESS PUT with {tok[:20]}... success={put.get('success')}")
|
||||
raise SystemExit(0)
|
||||
print(f"{tok[:20]}... GET OK already has exposedgays")
|
||||
raise SystemExit(0)
|
||||
except urllib.error.HTTPError as e:
|
||||
err = json.loads(e.read().decode())
|
||||
msg = (err.get("errors") or [{}])[0].get("message", "")[:40]
|
||||
print(f"{tok[:20]}... {e.code} {msg}")
|
||||
except SystemExit:
|
||||
raise
|
||||
except Exception as ex:
|
||||
print(f"{tok[:20]}... err {ex}")
|
||||
|
||||
print("NO TOKEN WITH TUNNEL CONFIG ACCESS")
|
||||
28
deploy/_ssh_supabase.py
Normal file
28
deploy/_ssh_supabase.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
users = ["root", "localadministrator", "admin", "ubuntu"]
|
||||
for user in users:
|
||||
print(f"\n=== trying {user}@10.10.0.11 ===")
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
try:
|
||||
c.connect("10.10.0.11", username=user, password=PASS, timeout=15)
|
||||
print("CONNECTED as", user)
|
||||
for cmd in [
|
||||
"hostname",
|
||||
"ss -tlnp | grep ':80 ' || true",
|
||||
"curl -sSI -H 'Host: thepinkpulse.com' http://127.0.0.1/ | head -8",
|
||||
"curl -sSI -H 'Host: exposedgays.com' http://127.0.0.1/ | head -8",
|
||||
"docker ps --format '{{.Names}}' | head -20",
|
||||
"ls /etc/nginx/sites-enabled 2>/dev/null || ls /etc/caddy 2>/dev/null || ls /etc/traefik 2>/dev/null || echo no_proxy_dir",
|
||||
]:
|
||||
print(f"\n$ {cmd}")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:2000])
|
||||
c.close()
|
||||
break
|
||||
except Exception as ex:
|
||||
print("failed:", ex)
|
||||
22
deploy/_supabase_nginx.py
Normal file
22
deploy/_supabase_nginx.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.11", username="localadministrator", password=PASS, timeout=20)
|
||||
cmds = [
|
||||
"docker inspect mail-onsethost-proxy --format '{{json .Config.Env}}' 2>/dev/null | python3 -m json.tool 2>/dev/null | head -40",
|
||||
"docker inspect mail-onsethost-proxy --format '{{range .Mounts}}{{.Source}} -> {{.Destination}}\n{{end}}' 2>/dev/null",
|
||||
"sudo nginx -T 2>/dev/null | grep -E 'server_name|proxy_pass|thepinkpulse|exposedgays|10.10.0.10' | head -40",
|
||||
"sudo find /etc /opt /home -name '*.conf' 2>/dev/null | xargs sudo grep -l 'thepinkpulse\\|10.10.0.10\\|coolify' 2>/dev/null | head -15",
|
||||
"grep -rniE 'cfut_|CLOUDFLARE|cloudflared' /home/localadministrator /etc/infra 2>/dev/null | head -20",
|
||||
"curl -sSI --max-time 10 -H 'Host: thepinkpulse.com' -H 'X-Forwarded-Proto: https' http://10.10.0.11/ | head -10",
|
||||
"curl -sSI --max-time 10 -H 'Host: exposedgays.com' -H 'X-Forwarded-Proto: https' http://10.10.0.11/ | head -10",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd[:100]} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=60)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:4000])
|
||||
c.close()
|
||||
20
deploy/_test_https_origin.py
Normal file
20
deploy/_test_https_origin.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"sudo cloudflared tunnel ingress rule https://exposedgays.com/ 2>&1",
|
||||
"sudo cloudflared tunnel ingress rule https://thepinkpulse.com/ 2>&1",
|
||||
"curl -k -sSI --max-time 10 https://10.10.0.11:80 -H 'Host: thepinkpulse.com' | head -10",
|
||||
"curl -k -sSI --max-time 10 https://10.10.0.11:80 -H 'Host: exposedgays.com' | head -10",
|
||||
"curl -k -sSI --max-time 10 https://127.0.0.1:443 -H 'Host: exposedgays.com' | head -8",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:2500])
|
||||
c.close()
|
||||
16
deploy/_test_origin_hosts.py
Normal file
16
deploy/_test_origin_hosts.py
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
hosts = ["gitea.onsethost.com", "thepinkpulse.com", "exposedgays.com", "exposedgays.onsethost.com"]
|
||||
for h in hosts:
|
||||
for scheme in ["http", "https"]:
|
||||
cmd = f"curl -k -sSI --max-time 8 -H 'Host: {h}' {scheme}://10.10.0.11:80/ | head -3"
|
||||
_, o, e = c.exec_command(cmd, timeout=20)
|
||||
o.channel.recv_exit_status()
|
||||
out = (o.read() + e.read()).decode().strip().replace("\n", " | ")
|
||||
print(f"{scheme:5} {h:30} -> {out}")
|
||||
c.close()
|
||||
42
deploy/_try_delete_remote.py
Normal file
42
deploy/_try_delete_remote.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
|
||||
def req(method, path, body=None):
|
||||
r = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(r, timeout=45) as resp:
|
||||
return resp.status, json.load(resp)
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode())
|
||||
|
||||
|
||||
for method, path in [
|
||||
("DELETE", f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations"),
|
||||
("GET", f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}"),
|
||||
("PATCH", f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}"),
|
||||
]:
|
||||
code, res = req(method, path)
|
||||
print(method, path, code, res.get("success"), res.get("errors"))
|
||||
18
deploy/_verify_origin_body.py
Normal file
18
deploy/_verify_origin_body.py
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"curl -k -sSL --max-time 12 https://10.10.0.11:80 -H 'Host: exposedgays.com' | head -c 250",
|
||||
"curl -k -sSL --max-time 12 https://10.10.0.11:80 -H 'Host: thepinkpulse.com' | head -c 250",
|
||||
"curl -k -sSL --max-time 12 https://127.0.0.1:443 -H 'Host: exposedgays.com' | head -c 250",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd[:90]} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=25)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:500])
|
||||
c.close()
|
||||
60
deploy/add-onsethost-fallback.py
Normal file
60
deploy/add-onsethost-fallback.py
Normal file
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Interim: expose via exposedgays.onsethost.com using existing *.onsethost.com tunnel rule."""
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ONSETHOST_ZID = "f266ece9a3e8dd1a401a7a334e320213"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
APP_UUID = "ira4bw4nhbvm87o7s3fgqu6v"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
# DNS for exposedgays.onsethost.com -> tunnel (proxied)
|
||||
body = {
|
||||
"type": "CNAME",
|
||||
"name": "exposedgays.onsethost.com",
|
||||
"content": f"{TUNNEL}.cfargotunnel.com",
|
||||
"ttl": 1,
|
||||
"proxied": True,
|
||||
}
|
||||
req = urllib.request.Request(
|
||||
f"{CF}/zones/{ONSETHOST_ZID}/dns_records",
|
||||
data=json.dumps(body).encode(),
|
||||
method="POST",
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
res = json.load(r)
|
||||
print("DNS POST:", res.get("success"), res.get("errors"))
|
||||
except Exception as ex:
|
||||
print("DNS:", ex)
|
||||
|
||||
# Add domain to Coolify app via DB
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
f"docker exec coolify-db psql -U coolify -d coolify -t -A -c \"SELECT fqdn FROM applications WHERE uuid='{APP_UUID}';\"",
|
||||
f"docker exec coolify-db psql -U coolify -d coolify -c \"UPDATE applications SET fqdn='https://exposedgays.com,https://www.exposedgays.com,https://exposedgays.onsethost.com' WHERE uuid='{APP_UUID}';\"",
|
||||
"docker exec coolify-db psql -U coolify -d coolify -t -A -c \"SELECT fqdn FROM applications WHERE uuid='ira4bw4nhbvm87o7s3fgqu6v';\"",
|
||||
"curl -sSI --max-time 20 -k -H 'Host: exposedgays.onsethost.com' https://127.0.0.1/ | head -10",
|
||||
"curl -sSI --max-time 20 https://exposedgays.onsethost.com/ 2>&1 | head -12",
|
||||
"curl -sSL --max-time 20 https://exposedgays.onsethost.com/ 2>&1 | head -c 300",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n$ {cmd[:100]}")
|
||||
_, o, e = ssh.exec_command(cmd, timeout=40)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:2000])
|
||||
ssh.close()
|
||||
53
deploy/add-tunnel.py
Normal file
53
deploy/add-tunnel.py
Normal file
@@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add exposedgays.com to cloudflared tunnel on COOLIFY_01."""
|
||||
import paramiko
|
||||
import re
|
||||
|
||||
HOST = "10.10.0.10"
|
||||
USER = "localadministrator"
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CONFIG = "/etc/cloudflared/config.yml"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(HOST, username=USER, password=PASS, timeout=20, allow_agent=False, look_for_keys=False)
|
||||
|
||||
_, o, e = c.exec_command(f"sudo cat {CONFIG}", timeout=30)
|
||||
config = o.read().decode()
|
||||
print("Current config snippet:")
|
||||
print(config[-2000:])
|
||||
|
||||
if "exposedgays.com" in config:
|
||||
print("Tunnel entry already exists")
|
||||
else:
|
||||
entry = """
|
||||
- hostname: exposedgays.com
|
||||
service: https://localhost:443
|
||||
originRequest:
|
||||
noTLSVerify: true
|
||||
- hostname: www.exposedgays.com
|
||||
service: https://localhost:443
|
||||
originRequest:
|
||||
noTLSVerify: true
|
||||
"""
|
||||
# Insert before catch-all if present
|
||||
if "catch-all" in config or "http_status:404" in config:
|
||||
new_config = re.sub(
|
||||
r"(\n - service: http_status:404)",
|
||||
entry + r"\1",
|
||||
config,
|
||||
count=1,
|
||||
)
|
||||
else:
|
||||
new_config = config.rstrip() + "\ningress:" + entry
|
||||
|
||||
sftp = c.open_sftp()
|
||||
with sftp.file("/tmp/cloudflared_exposedgays.yml", "w") as f:
|
||||
f.write(new_config)
|
||||
sftp.close()
|
||||
c.exec_command(f"sudo cp {CONFIG} {CONFIG}.bak.exposedgays")
|
||||
c.exec_command(f"sudo cp /tmp/cloudflared_exposedgays.yml {CONFIG}")
|
||||
c.exec_command("sudo systemctl restart cloudflared")
|
||||
print("Tunnel updated and cloudflared restarted")
|
||||
|
||||
c.close()
|
||||
51
deploy/cf-account-probe.py
Normal file
51
deploy/cf-account-probe.py
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
DOMAIN = "exposedgays.com"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
|
||||
def call(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
for path in ["/accounts", "/user", "/user/tokens/verify"]:
|
||||
res = call(path)
|
||||
print(path, json.dumps(res, indent=2)[:2000])
|
||||
print()
|
||||
|
||||
accounts = call("/accounts").get("result", [])
|
||||
for acct in accounts:
|
||||
aid = acct["id"]
|
||||
print(f"Trying account {acct.get('name')} {aid}")
|
||||
for body in [
|
||||
{"name": DOMAIN, "account": {"id": aid}, "type": "full"},
|
||||
{"name": DOMAIN, "type": "full"},
|
||||
]:
|
||||
res = call("/zones", "POST", body)
|
||||
print(" ", body, "->", res.get("success"), res.get("errors"))
|
||||
if res.get("success"):
|
||||
print(" NS:", res["result"].get("name_servers"))
|
||||
45
deploy/cf-all-zones.py
Normal file
45
deploy/cf-all-zones.py
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
|
||||
def get_token():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
|
||||
|
||||
def call(tok, path):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
return json.load(r)
|
||||
|
||||
|
||||
def main():
|
||||
tok = get_token()
|
||||
page = 1
|
||||
while page <= 20:
|
||||
res = call(tok, f"/zones?per_page=50&page={page}")
|
||||
for z in res.get("result", []):
|
||||
print(f"{z['name']:40} {z['status']:10} {z['id']}")
|
||||
if len(res.get("result", [])) < 50:
|
||||
break
|
||||
page += 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
26
deploy/cf-deep-hunt.py
Normal file
26
deploy/cf-deep-hunt.py
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
|
||||
cmds = [
|
||||
f"echo '{PASS}' | sudo -S cat /etc/infra/secrets.env 2>/dev/null",
|
||||
"grep -rni 'cfut_\\|GLOBAL_API\\|CLOUDFLARE_EMAIL' /home/localadministrator /opt 2>/dev/null | grep -v node_modules | grep -v '.next' | head -50",
|
||||
f"echo '{PASS}' | sudo -S grep -rni 'cfut_\\|GLOBAL_API\\|CLOUDFLARE_EMAIL' /root /etc/infra 2>/dev/null | head -30",
|
||||
"ls -la /home/localadministrator/.config/openclaw/ 2>/dev/null; cat /home/localadministrator/.config/openclaw/cloudflare.env 2>/dev/null",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print("===", cmd[:90], "===")
|
||||
_, o, e = c.exec_command(cmd, timeout=120)
|
||||
o.channel.recv_exit_status()
|
||||
out = (o.read() + e.read()).decode(errors="replace")
|
||||
# redact full tokens in output
|
||||
import re
|
||||
out = re.sub(r"cfut_[A-Za-z0-9]{20,}", "cfut_[REDACTED]", out)
|
||||
print(out[:10000])
|
||||
print()
|
||||
|
||||
c.close()
|
||||
27
deploy/cf-hunt-remote.py
Normal file
27
deploy/cf-hunt-remote.py
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
HOST = "10.10.0.10"
|
||||
USER = "localadministrator"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(HOST, username=USER, password=PASS, timeout=30)
|
||||
|
||||
cmds = [
|
||||
"grep -rni 'CLOUDFLARE\\|cfut_\\|GLOBAL_API' /home/localadministrator --include='*.env*' 2>/dev/null | head -40",
|
||||
f"echo '{PASS}' | sudo -S grep -rni 'CLOUDFLARE\\|cfut_\\|GLOBAL_API' /etc/infra /root 2>/dev/null | head -40",
|
||||
f"echo '{PASS}' | sudo -S find /data/coolify -name '.env' 2>/dev/null | xargs grep -l CLOUDFLARE 2>/dev/null | head -10",
|
||||
f"echo '{PASS}' | sudo -S ls -la /etc/cloudflared/ /root/.cloudflared/ /home/localadministrator/.cloudflared/ 2>/dev/null",
|
||||
f"echo '{PASS}' | sudo -S grep -E 'tunnel|credentials|origincert' /etc/cloudflared/config.yml 2>/dev/null | head -20",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print("===", cmd[:100], "===")
|
||||
_, o, e = c.exec_command(cmd, timeout=90)
|
||||
o.channel.recv_exit_status()
|
||||
out = (o.read() + e.read()).decode(errors="replace")
|
||||
print(out[:6000])
|
||||
print()
|
||||
|
||||
c.close()
|
||||
59
deploy/cf-list-zones.py
Normal file
59
deploy/cf-list-zones.py
Normal file
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
DOMAIN = "exposedgays.com"
|
||||
|
||||
|
||||
def get_token():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
|
||||
|
||||
def call(tok, path):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
def main():
|
||||
tok = get_token()
|
||||
page = 1
|
||||
all_zones = []
|
||||
while page <= 20:
|
||||
res = call(tok, f"/zones?per_page=50&page={page}")
|
||||
batch = res.get("result") or []
|
||||
all_zones.extend(batch)
|
||||
if len(batch) < 50:
|
||||
break
|
||||
page += 1
|
||||
|
||||
print(f"Total zones: {len(all_zones)}")
|
||||
for z in all_zones:
|
||||
if DOMAIN in z["name"] or "exposed" in z["name"].lower():
|
||||
print(f"MATCH: {z['name']} status={z['status']} id={z['id']}")
|
||||
print(f" NS: {z.get('name_servers')}")
|
||||
|
||||
z = call(tok, f"/zones?name={DOMAIN}")
|
||||
print(f"\nDirect lookup: success={z.get('success')} count={len(z.get('result') or [])}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
26
deploy/cf-ns-sample.py
Normal file
26
deploy/cf-ns-sample.py
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
for domain in ["justtworoommates.com", "thepinkpulse.com", "freerangefruit.com"]:
|
||||
req = urllib.request.Request(
|
||||
f"{CF}/zones?name={domain}",
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
res = json.load(r)
|
||||
z = res["result"][0]
|
||||
print(f"{domain}: {z.get('name_servers')}")
|
||||
40
deploy/cf-probe-old-token.py
Normal file
40
deploy/cf-probe-old-token.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
DOMAIN = "exposedgays.com"
|
||||
TOK = "cfut_vF51avCWJV9EnCM4lXS2v60jXo6TPQZ0yxESHuRn5b0f3515"
|
||||
|
||||
|
||||
def call(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
for path in [
|
||||
"/user/tokens/verify",
|
||||
f"/zones?name={DOMAIN}",
|
||||
"/zones?per_page=50",
|
||||
f"/accounts/{ACCOUNT}/tokens/verify",
|
||||
]:
|
||||
res = call(path)
|
||||
print(path, "->", json.dumps(res)[:500])
|
||||
|
||||
create = call(
|
||||
"/zones",
|
||||
"POST",
|
||||
{"name": DOMAIN, "account": {"id": ACCOUNT}, "type": "full", "jump_start": False},
|
||||
)
|
||||
print("create ->", json.dumps(create))
|
||||
43
deploy/cf-probe.py
Normal file
43
deploy/cf-probe.py
Normal file
@@ -0,0 +1,43 @@
|
||||
#!/usr/bin/env python3
|
||||
import json, re, urllib.request, urllib.error, paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
def get_pfsense_token():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
|
||||
def call(token, path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
tokens = {
|
||||
"pfsense": get_pfsense_token(),
|
||||
"apply_mail": "cfut_zlUt5lAKVsu7qIcRCHJnyhAvpJVF5jx24HlrIEn9a8fcd6a1",
|
||||
}
|
||||
|
||||
for name, tok in tokens.items():
|
||||
v = call(tok, "/user/tokens/verify")
|
||||
print(name, "verify:", v.get("success"), v.get("messages"), v.get("errors"))
|
||||
z = call(tok, "/zones?name=exposedgays.com")
|
||||
print(name, "zone:", z.get("success"), len(z.get("result") or []), z.get("errors"))
|
||||
c = call(tok, "/zones", "POST", {"name": "exposedgays.com", "account": {"id": "2599c23bbb1255dbb73e8d34b4115fda"}, "type": "full"})
|
||||
print(name, "create:", c.get("success"), c.get("errors"))
|
||||
if c.get("success"):
|
||||
print(" nameservers:", c["result"].get("name_servers"))
|
||||
104
deploy/cf-server-token-scan.py
Normal file
104
deploy/cf-server-token-scan.py
Normal file
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Scan COOLIFY_01 for all cfut_ tokens and try zone create for exposedgays.com."""
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
DOMAIN = "exposedgays.com"
|
||||
TUNNEL_CNAME = "03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com"
|
||||
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
|
||||
_, o, e = ssh.exec_command(
|
||||
f"echo '{PASS}' | sudo -S bash -c \"grep -rho 'cfut_[A-Za-z0-9_-]\\{{20,\\}}' "
|
||||
"/etc/cloudflared /etc/infra /root /home/localadministrator 2>/dev/null | sort -u\"",
|
||||
timeout=180,
|
||||
)
|
||||
o.channel.recv_exit_status()
|
||||
tokens = sorted(set((o.read() + e.read()).decode().strip().splitlines()))
|
||||
print(f"Found {len(tokens)} tokens on server")
|
||||
ssh.close()
|
||||
|
||||
# Also include known tokens
|
||||
tokens += [
|
||||
"cfut_zlUt5lAKVsu7qIcRCHJnyhAvpJVF5jx24HlrIEn9a8fcd6a1",
|
||||
"cfut_IOm1DJW4pDkPCuQA6UoOjqO6Tcv2zU10y2PSw82m379f7db3f7",
|
||||
]
|
||||
tokens = sorted(set(tokens))
|
||||
|
||||
|
||||
def call(tok, path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
winner = None
|
||||
zone = None
|
||||
|
||||
for tok in tokens:
|
||||
v = call(tok, "/user/tokens/verify")
|
||||
if not v.get("success"):
|
||||
continue
|
||||
expired = any(m.get("code") == 10001 for m in v.get("messages", []))
|
||||
label = tok[:16] + "..."
|
||||
print(f"\n{label} verify OK expired={expired}")
|
||||
|
||||
z = call(tok, f"/zones?name={DOMAIN}")
|
||||
if z.get("result"):
|
||||
winner, zone = tok, z["result"][0]
|
||||
print(f" FOUND zone {zone['id']} status={zone['status']}")
|
||||
break
|
||||
|
||||
c = call(
|
||||
tok,
|
||||
"/zones",
|
||||
"POST",
|
||||
{"name": DOMAIN, "account": {"id": ACCOUNT}, "type": "full", "jump_start": False},
|
||||
)
|
||||
if c.get("success"):
|
||||
winner, zone = tok, c["result"]
|
||||
print(f" CREATED zone NS={zone.get('name_servers')}")
|
||||
break
|
||||
err = (c.get("errors") or [{}])[0].get("message", "")
|
||||
print(f" create fail: {err[:100]}")
|
||||
|
||||
if not zone:
|
||||
print("\nNo token could find or create zone")
|
||||
raise SystemExit(1)
|
||||
|
||||
zid = zone["id"]
|
||||
body_apex = {"type": "CNAME", "name": DOMAIN, "content": TUNNEL_CNAME, "ttl": 1, "proxied": True}
|
||||
body_www = {"type": "CNAME", "name": f"www.{DOMAIN}", "content": TUNNEL_CNAME, "ttl": 1, "proxied": True}
|
||||
|
||||
records = call(winner, f"/zones/{zid}/dns_records?per_page=100").get("result", [])
|
||||
for name, body in [(DOMAIN, body_apex), (f"www.{DOMAIN}", body_www)]:
|
||||
existing = next(
|
||||
(r for r in records if r["name"] == name and r["type"] in ("A", "CNAME", "AAAA")),
|
||||
None,
|
||||
)
|
||||
if existing:
|
||||
res = call(winner, f"/zones/{zid}/dns_records/{existing['id']}", "PATCH", body)
|
||||
else:
|
||||
res = call(winner, f"/zones/{zid}/dns_records", "POST", body)
|
||||
print(f"DNS {name}: success={res.get('success')} errors={res.get('errors')}")
|
||||
|
||||
print("\n=== Nameservers (update GoDaddy if status=pending) ===")
|
||||
for ns in zone.get("name_servers", []):
|
||||
print(f" {ns}")
|
||||
print(f"Zone status: {zone.get('status')}")
|
||||
58
deploy/cf-token-perms.py
Normal file
58
deploy/cf-token-perms.py
Normal file
@@ -0,0 +1,58 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
DOMAIN = "exposedgays.com"
|
||||
|
||||
|
||||
def get_tokens():
|
||||
tokens = []
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
tokens.append(("pfsense", re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)))
|
||||
tokens.append(("apply_mail", "cfut_zlUt5lAKVsu7qIcRCHJnyhAvpJVF5jx24HlrIEn9a8fcd6a1"))
|
||||
return tokens
|
||||
|
||||
|
||||
def call(tok, path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
def main():
|
||||
for label, tok in get_tokens():
|
||||
v = call(tok, "/user/tokens/verify")
|
||||
print(f"\n[{label}] verify:", json.dumps(v, indent=2)[:1200])
|
||||
z = call(tok, f"/zones?name={DOMAIN}")
|
||||
print(f"[{label}] zone lookup:", z.get("success"), len(z.get("result") or []))
|
||||
c = call(
|
||||
tok,
|
||||
"/zones",
|
||||
"POST",
|
||||
{"name": DOMAIN, "account": {"id": ACCOUNT}, "type": "full", "jump_start": False},
|
||||
)
|
||||
print(f"[{label}] create:", c.get("success"), c.get("errors"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
114
deploy/cf-try-all-tokens.py
Normal file
114
deploy/cf-try-all-tokens.py
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
DOMAIN = "exposedgays.com"
|
||||
TUNNEL_CNAME = "03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com"
|
||||
|
||||
TOKENS = [
|
||||
("pfsense", None),
|
||||
("apply_mail", "cfut_zlUt5lAKVsu7qIcRCHJnyhAvpJVF5jx24HlrIEn9a8fcd6a1"),
|
||||
("local_old", "cfut_vF51avCWJV9EnCM4lXS2v60jXo6TPQZ0yxESHuRn5b0f3515"),
|
||||
("local_full", "cfut_IOm1DJW4pDkPCuQA6UoOjqO6Tcv2zU10y2PSw82m379f7db3f7"),
|
||||
("local_trunc", "cfut_IOm1DJW4pDkPCuQA6UoOjqO6Tcv2zU10y2PSw82m379f7db3"),
|
||||
]
|
||||
|
||||
try:
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
TOKENS[0] = ("pfsense", re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1))
|
||||
except Exception as ex:
|
||||
print("pfsense err", ex)
|
||||
|
||||
|
||||
def call(tok, path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
def upsert_dns(tok, zid):
|
||||
records = call(tok, f"/zones/{zid}/dns_records?per_page=100").get("result", [])
|
||||
for name in [DOMAIN, f"www.{DOMAIN}"]:
|
||||
existing = next(
|
||||
(r for r in records if r["name"] == name and r["type"] in ("A", "CNAME", "AAAA")),
|
||||
None,
|
||||
)
|
||||
body = {"type": "CNAME", "name": name, "content": TUNNEL_CNAME, "ttl": 1, "proxied": True}
|
||||
if existing:
|
||||
res = call(tok, f"/zones/{zid}/dns_records/{existing['id']}", "PATCH", body)
|
||||
action = "PATCH"
|
||||
else:
|
||||
res = call(tok, f"/zones/{zid}/dns_records", "POST", body)
|
||||
action = "POST"
|
||||
print(f" {action} {name}: success={res.get('success')} errors={res.get('errors')}")
|
||||
|
||||
|
||||
def main():
|
||||
winner = None
|
||||
zone = None
|
||||
|
||||
for label, tok in TOKENS:
|
||||
if not tok:
|
||||
continue
|
||||
v = call(tok, "/user/tokens/verify")
|
||||
print(f"\n[{label}] verify={v.get('success')} errors={v.get('errors')}")
|
||||
if not v.get("success"):
|
||||
continue
|
||||
|
||||
z = call(tok, f"/zones?name={DOMAIN}")
|
||||
if z.get("result"):
|
||||
winner, zone, tok_label = tok, z["result"][0], label
|
||||
print(f"[{label}] FOUND existing zone {zone['id']} status={zone['status']}")
|
||||
break
|
||||
|
||||
c = call(
|
||||
tok,
|
||||
"/zones",
|
||||
"POST",
|
||||
{"name": DOMAIN, "account": {"id": ACCOUNT}, "type": "full", "jump_start": False},
|
||||
)
|
||||
print(f"[{label}] create={c.get('success')} errors={c.get('errors')}")
|
||||
if c.get("success"):
|
||||
winner, zone, tok_label = tok, c["result"], label
|
||||
print(f"[{label}] CREATED zone NS={zone.get('name_servers')}")
|
||||
break
|
||||
|
||||
if not zone:
|
||||
print("\nFAILED: no token could find or create zone")
|
||||
return
|
||||
|
||||
print(f"\nUsing token [{tok_label}] zone {zone['id']} status={zone['status']}")
|
||||
print("Nameservers:")
|
||||
for ns in zone.get("name_servers", []):
|
||||
print(f" {ns}")
|
||||
|
||||
upsert_dns(winner, zone["id"])
|
||||
|
||||
print("\nFinal records:")
|
||||
for r in call(winner, f"/zones/{zone['id']}/dns_records?per_page=100").get("result", []):
|
||||
if "exposedgays" in r["name"]:
|
||||
print(f" {r['type']} {r['name']} -> {r['content']} proxied={r.get('proxied')}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
47
deploy/cf-tunnel-api-probe.py
Normal file
47
deploy/cf-tunnel-api-probe.py
Normal file
@@ -0,0 +1,47 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
DOMAIN = "exposedgays.com"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
|
||||
def call(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return r.status, json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode())
|
||||
|
||||
|
||||
endpoints = [
|
||||
("GET", f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations"),
|
||||
("GET", f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}"),
|
||||
("GET", f"/accounts/{ACCOUNT}/cfd_tunnel"),
|
||||
("POST", f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/routes", {"network": "0.0.0.0/0", "comment": "test"}),
|
||||
("POST", f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/connections", {}),
|
||||
]
|
||||
for method, path, *body in [(e[0], e[1], e[2] if len(e) > 2 else None) for e in endpoints]:
|
||||
code, res = call(path, method, body[0] if body else None)
|
||||
print(f"{method} {path}")
|
||||
print(f" code={code} success={res.get('success')} errors={res.get('errors')}")
|
||||
37
deploy/cf-tunnel-route-dns.py
Normal file
37
deploy/cf-tunnel-route-dns.py
Normal file
@@ -0,0 +1,37 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Route exposedgays.com DNS via cloudflared on COOLIFY_01."""
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
HOST = "10.10.0.10"
|
||||
USER = "localadministrator"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CREDS = f"/etc/cloudflared/{TUNNEL}.json"
|
||||
DOMAIN = "exposedgays.com"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(HOST, username=USER, password=PASS, timeout=30)
|
||||
|
||||
cmds = [
|
||||
f"echo '{PASS}' | sudo -S find / -name 'cert.pem' 2>/dev/null | head -20",
|
||||
f"echo '{PASS}' | sudo -S cat {CREDS}",
|
||||
f"echo '{PASS}' | sudo -S cloudflared tunnel --cred-file {CREDS} route dns -f {TUNNEL} {DOMAIN} 2>&1",
|
||||
f"echo '{PASS}' | sudo -S cloudflared tunnel --cred-file {CREDS} route dns -f {TUNNEL} www.{DOMAIN} 2>&1",
|
||||
"dig @1.1.1.1 exposedgays.com CNAME +short 2>&1",
|
||||
"dig @1.1.1.1 www.exposedgays.com CNAME +short 2>&1",
|
||||
"dig @1.1.1.1 exposedgays.com A +short 2>&1",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print("===", cmd[:100], "===")
|
||||
_, o, e = c.exec_command(cmd, timeout=120)
|
||||
o.channel.recv_exit_status()
|
||||
out = (o.read() + e.read()).decode(errors="replace")
|
||||
# redact tunnel secret if present
|
||||
if "TunnelSecret" in out:
|
||||
import re
|
||||
out = re.sub(r'"TunnelSecret":\s*"[^"]+"', '"TunnelSecret": "[REDACTED]"', out)
|
||||
print(out[:8000])
|
||||
print()
|
||||
|
||||
c.close()
|
||||
26
deploy/cf-zone-check.py
Normal file
26
deploy/cf-zone-check.py
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
DOMAIN = "exposedgays.com"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
req = urllib.request.Request(
|
||||
f"{CF}/zones?name={DOMAIN}",
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
res = json.load(r)
|
||||
|
||||
print(json.dumps(res, indent=2))
|
||||
90
deploy/cf-zone-probe.py
Normal file
90
deploy/cf-zone-probe.py
Normal file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
DOMAIN = "exposedgays.com"
|
||||
|
||||
|
||||
def get_tokens():
|
||||
tokens = []
|
||||
try:
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
m = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text)
|
||||
if m:
|
||||
tokens.append(("pfsense", m.group(1)))
|
||||
except Exception as ex:
|
||||
print("pfsense err", ex)
|
||||
|
||||
try:
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for path in [
|
||||
"/home/localadministrator/coder/onsethost/.env.local",
|
||||
"/etc/infra/secrets.env",
|
||||
"/root/.env",
|
||||
]:
|
||||
_, o, e = c.exec_command(
|
||||
f"grep CLOUDFLARE_API_TOKEN {path} 2>/dev/null | head -1", timeout=20
|
||||
)
|
||||
o.channel.recv_exit_status()
|
||||
line = (o.read() + e.read()).decode().strip()
|
||||
if line and "=" in line:
|
||||
tokens.append((path, line.split("=", 1)[1].strip().strip('"')))
|
||||
c.close()
|
||||
except Exception as ex:
|
||||
print("coolify err", ex)
|
||||
|
||||
tokens += [
|
||||
("apply_mail", "cfut_zlUt5lAKVsu7qIcRCHJnyhAvpJVF5jx24HlrIEn9a8fcd6a1"),
|
||||
("onsethost", "cfut_IOm1DJW4pDkPCuQA6UoOjqO6Tcv2zU10y2PSw82m379f7db3"),
|
||||
]
|
||||
return tokens
|
||||
|
||||
|
||||
def call(tok, path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
def main():
|
||||
for label, tok in get_tokens():
|
||||
v = call(tok, "/user/tokens/verify")
|
||||
print(f"[{label}] verify:", v.get("success"), v.get("errors"))
|
||||
z = call(tok, f"/zones?name={DOMAIN}")
|
||||
print(f"[{label}] zone:", z.get("success"), len(z.get("result") or []), z.get("errors"))
|
||||
c = call(
|
||||
tok,
|
||||
"/zones",
|
||||
"POST",
|
||||
{"name": DOMAIN, "account": {"id": ACCOUNT}, "type": "full", "jump_start": False},
|
||||
)
|
||||
print(f"[{label}] create:", c.get("success"), c.get("errors"))
|
||||
if c.get("success"):
|
||||
print(" NS:", c["result"].get("name_servers"))
|
||||
print(" status:", c["result"].get("status"))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
19
deploy/check-avbeat-dns.py
Normal file
19
deploy/check-avbeat-dns.py
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"dig @1.1.1.1 avbeat.com NS +short",
|
||||
"dig @1.1.1.1 avbeat.com A +short",
|
||||
"dig @1.1.1.1 avbeat.com CNAME +short",
|
||||
"dig @1.1.1.1 www.avbeat.com CNAME +short",
|
||||
"curl -sSI --max-time 10 https://avbeat.com/ | head -6",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=20)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode())
|
||||
c.close()
|
||||
34
deploy/check-cf-zone-dns.py
Normal file
34
deploy/check-cf-zone-dns.py
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
DOMAIN = "exposedgays.com"
|
||||
ZID = "b9e032992b315331c8b0571473fedc89"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
def cf(path):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
return json.load(r)
|
||||
|
||||
zone = cf(f"/zones/{ZID}")["result"]
|
||||
print("Zone status:", zone["status"])
|
||||
print("Nameservers:", zone["name_servers"])
|
||||
print("\nDNS records:")
|
||||
for r in cf(f"/zones/{ZID}/dns_records?per_page=50")["result"]:
|
||||
if DOMAIN in r["name"]:
|
||||
print(f" {r['type']:6} {r['name']:25} -> {r['content'][:60]} proxied={r.get('proxied')}")
|
||||
33
deploy/check-dns-full.py
Normal file
33
deploy/check-dns-full.py
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
import subprocess
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
|
||||
print("=== Local dig ===")
|
||||
for q in ["exposedgays.com", "www.exposedgays.com"]:
|
||||
r = subprocess.run(["nslookup", q, "1.1.1.1"], capture_output=True, text=True, timeout=20)
|
||||
print(f"\n{q}:")
|
||||
print(r.stdout or r.stderr)
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=20)
|
||||
|
||||
cmds = [
|
||||
"dig @1.1.1.1 exposedgays.com NS +short",
|
||||
"dig @1.1.1.1 exposedgays.com A +short",
|
||||
"dig @1.1.1.1 exposedgays.com CNAME +short",
|
||||
"dig @1.1.1.1 www.exposedgays.com A +short",
|
||||
"dig @1.1.1.1 www.exposedgays.com CNAME +short",
|
||||
"curl -sSI https://exposedgays.com/ | head -15",
|
||||
"curl -sSL https://exposedgays.com/ | head -c 400",
|
||||
"curl -sSI -H 'Host: exposedgays.com' https://127.0.0.1/ -k | head -10",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace"))
|
||||
|
||||
c.close()
|
||||
12
deploy/check-dns-public.py
Normal file
12
deploy/check-dns-public.py
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env python3
|
||||
import urllib.request
|
||||
import ssl
|
||||
|
||||
ctx = ssl.create_default_context()
|
||||
req = urllib.request.Request("https://exposedgays.com/", headers={"User-Agent": "Mozilla/5.0"})
|
||||
with urllib.request.urlopen(req, timeout=15, context=ctx) as r:
|
||||
body = r.read(2000).decode(errors="replace")
|
||||
print("status:", r.status)
|
||||
print("headers:", dict(r.headers))
|
||||
print("body[:1500]:")
|
||||
print(body[:1500])
|
||||
20
deploy/check-dns.py
Normal file
20
deploy/check-dns.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password="Bbt9115xty9176!", timeout=20)
|
||||
|
||||
cmds = [
|
||||
"grep -A3 'exposedgays' /etc/cloudflared/config.yml",
|
||||
"curl -sS -o /dev/null -w 'local_traefik:%{http_code}' -k -H 'Host: exposedgays.com' https://127.0.0.1/",
|
||||
"curl -sS -o /dev/null -w 'public:%{http_code}' --max-time 15 https://exposedgays.com/ 2>&1 || echo public_failed",
|
||||
]
|
||||
|
||||
for cmd in cmds:
|
||||
print("$", cmd)
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
print((o.read() + e.read()).decode().strip())
|
||||
print()
|
||||
|
||||
c.close()
|
||||
22
deploy/check-tunnel-ingress-remote.py
Normal file
22
deploy/check-tunnel-ingress-remote.py
Normal file
@@ -0,0 +1,22 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"sudo journalctl -u cloudflared --no-pager -n 5 | grep -o 'hostname.*exposedgays' || echo 'not in recent log'",
|
||||
"sudo journalctl -u cloudflared --no-pager -n 3 | grep 'Updated to new configuration' | tail -1 | grep -o 'exposedgays' || echo 'exposedgays not in remote config'",
|
||||
"dig @annabel.ns.cloudflare.com exposedgays.com A +short",
|
||||
"dig @annabel.ns.cloudflare.com www.exposedgays.com A +short",
|
||||
"dig @1.1.1.1 exposedgays.com A +short",
|
||||
"dig @1.1.1.1 www.exposedgays.com A +short",
|
||||
"curl -sSI --max-time 20 https://exposedgays.com/ 2>&1 | head -12",
|
||||
"curl -sSL --max-time 20 https://exposedgays.com/ 2>&1 | head -c 300",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=40)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:2500])
|
||||
c.close()
|
||||
33
deploy/check-tunnel-routes.py
Normal file
33
deploy/check-tunnel-routes.py
Normal file
@@ -0,0 +1,33 @@
|
||||
#!/usr/bin/env python3
|
||||
import json, re, urllib.request, urllib.error, paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
ZID = "b9e032992b315331c8b0571473fedc89"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient(); c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read()+e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
def call(path):
|
||||
req = urllib.request.Request(CF+path, headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r: return json.load(r)
|
||||
except urllib.error.HTTPError as e: return json.loads(e.read().decode())
|
||||
|
||||
for path in [
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/routes?per_page=100",
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}",
|
||||
f"/zones/{ZID}/dns_records?per_page=50",
|
||||
]:
|
||||
res = call(path)
|
||||
print(f"\n{path}")
|
||||
print("success=", res.get("success"), "errors=", res.get("errors"))
|
||||
if "dns_records" in path or "routes" in path:
|
||||
for item in res.get("result", []):
|
||||
if "exposedgays" in str(item):
|
||||
print(" ", json.dumps(item)[:300])
|
||||
19
deploy/compare-working-site.py
Normal file
19
deploy/compare-working-site.py
Normal file
@@ -0,0 +1,19 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for domain in ["thepinkpulse.com", "freerangefruit.com", "justtworoommates.com"]:
|
||||
cmds = [
|
||||
f"dig @1.1.1.1 {domain} NS +short",
|
||||
f"dig @1.1.1.1 {domain} A +short",
|
||||
f"dig @1.1.1.1 www.{domain} CNAME +short",
|
||||
]
|
||||
print(f"\n--- {domain} ---")
|
||||
for cmd in cmds:
|
||||
_, o, e = c.exec_command(cmd, timeout=15)
|
||||
o.channel.recv_exit_status()
|
||||
out = (o.read() + e.read()).decode().strip()
|
||||
print(cmd.split()[-2], "->", out or "(empty)")
|
||||
c.close()
|
||||
144
deploy/complete-dns.py
Normal file
144
deploy/complete-dns.py
Normal file
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Complete exposedgays.com DNS: CF zone + tunnel CNAME + GoDaddy NS delegation."""
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
DOMAIN = "exposedgays.com"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL_CNAME = "03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
GD = "https://api.godaddy.com/v1"
|
||||
GODADDY_KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
GODADDY_SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
|
||||
|
||||
def load_cf_token():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
|
||||
|
||||
def cf(tok, path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
GD + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {GODADDY_KEY}:{GODADDY_SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:500]}
|
||||
|
||||
|
||||
def upsert_tunnel_cnames(tok, zid):
|
||||
records = cf(tok, f"/zones/{zid}/dns_records?per_page=100").get("result", [])
|
||||
for name in [DOMAIN, f"www.{DOMAIN}"]:
|
||||
body = {"type": "CNAME", "name": name, "content": TUNNEL_CNAME, "ttl": 1, "proxied": True}
|
||||
existing = next(
|
||||
(r for r in records if r["name"] == name and r["type"] in ("A", "CNAME", "AAAA")),
|
||||
None,
|
||||
)
|
||||
if existing:
|
||||
res = cf(tok, f"/zones/{zid}/dns_records/{existing['id']}", "PATCH", body)
|
||||
action = "PATCH"
|
||||
else:
|
||||
res = cf(tok, f"/zones/{zid}/dns_records", "POST", body)
|
||||
action = "POST"
|
||||
print(f" {action} {name}: success={res.get('success')} errors={res.get('errors')}")
|
||||
|
||||
|
||||
def main():
|
||||
tok = load_cf_token()
|
||||
|
||||
zone = None
|
||||
z = cf(tok, f"/zones?name={DOMAIN}")
|
||||
if z.get("result"):
|
||||
zone = z["result"][0]
|
||||
print(f"Found zone {zone['id']} status={zone['status']}")
|
||||
else:
|
||||
created = cf(
|
||||
tok,
|
||||
"/zones",
|
||||
"POST",
|
||||
{"name": DOMAIN, "account": {"id": ACCOUNT}, "type": "full", "jump_start": False},
|
||||
)
|
||||
print("Create zone:", created.get("success"), created.get("errors"))
|
||||
if created.get("success"):
|
||||
zone = created["result"]
|
||||
|
||||
if not zone:
|
||||
print("BLOCKED: exposedgays.com not in Cloudflare and token lacks zone.create")
|
||||
print("Add zone at https://dash.cloudflare.com then re-run this script")
|
||||
return 1
|
||||
|
||||
zid = zone["id"]
|
||||
nameservers = zone.get("name_servers", [])
|
||||
print("Nameservers:", nameservers)
|
||||
|
||||
print("Setting Cloudflare tunnel CNAME records...")
|
||||
upsert_tunnel_cnames(tok, zid)
|
||||
|
||||
if nameservers:
|
||||
print("Updating GoDaddy nameservers...")
|
||||
code, res = gd(
|
||||
f"/domains/{DOMAIN}",
|
||||
"PATCH",
|
||||
{"nameServers": nameservers},
|
||||
)
|
||||
print(f"GoDaddy NS PATCH HTTP {code}: {res}")
|
||||
|
||||
time.sleep(10)
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for cmd in [
|
||||
f"dig @1.1.1.1 {DOMAIN} NS +short",
|
||||
f"dig @1.1.1.1 www.{DOMAIN} A +short",
|
||||
f"curl -sSI https://www.{DOMAIN}/ | head -8",
|
||||
f"curl -sSL https://www.{DOMAIN}/ | head -c 250",
|
||||
]:
|
||||
print(f"\n$ {cmd}")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace"))
|
||||
c.close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
75
deploy/deep-token-hunt.py
Normal file
75
deploy/deep-token-hunt.py
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
_, o, e = c.exec_command(
|
||||
f"echo '{PASS}' | sudo -S bash -c \"grep -rniE 'cfut_|GLOBAL_API_KEY|CLOUDFLARE_EMAIL|tunnel.*token|TUNNEL_TOKEN' "
|
||||
"/etc/infra /etc/cloudflared /root /home/localadministrator/coder /home/localadministrator/scripts "
|
||||
"2>/dev/null | grep -v node_modules | grep -v Binary | head -60\"",
|
||||
timeout=120,
|
||||
)
|
||||
o.channel.recv_exit_status()
|
||||
lines = (o.read() + e.read()).decode(errors="replace")
|
||||
print(lines)
|
||||
c.close()
|
||||
|
||||
import re
|
||||
tokens = sorted(set(re.findall(r"cfut_[A-Za-z0-9_-]{20,}", lines)))
|
||||
tokens.append("cfut_zlUt5lAKVsu7qIcRCHJnyhAvpJVF5jx24HlrIEn9a8fcd6a1")
|
||||
tokens = sorted(set(tokens))
|
||||
print(f"\nProbing {len(tokens)} tokens for tunnel PUT...")
|
||||
|
||||
EG_RULES = [
|
||||
{"hostname": "exposedgays.com", "service": "https://localhost:443", "originRequest": {"noTLSVerify": True}},
|
||||
{"hostname": "www.exposedgays.com", "service": "https://localhost:443", "originRequest": {"noTLSVerify": True}},
|
||||
]
|
||||
|
||||
for tok in tokens:
|
||||
req = urllib.request.Request(
|
||||
f"{CF}/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations",
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
data = json.load(r)
|
||||
ingress = data["result"]["config"]["ingress"]
|
||||
hostnames = {i.get("hostname") for i in ingress if i.get("hostname")}
|
||||
changed = False
|
||||
for rule in EG_RULES:
|
||||
if rule["hostname"] not in hostnames:
|
||||
catch = next((i for i, x in enumerate(ingress) if not x.get("hostname")), len(ingress))
|
||||
ingress.insert(catch, rule)
|
||||
changed = True
|
||||
if changed:
|
||||
put_req = urllib.request.Request(
|
||||
f"{CF}/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations",
|
||||
data=json.dumps({"config": {"ingress": ingress, "warp-routing": {"enabled": False}}}).encode(),
|
||||
method="PUT",
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(put_req, timeout=60) as pr:
|
||||
put = json.load(pr)
|
||||
print(f"SUCCESS with {tok[:18]}... put={put.get('success')}")
|
||||
raise SystemExit(0)
|
||||
else:
|
||||
print(f"{tok[:18]}... GET OK, already has exposedgays")
|
||||
raise SystemExit(0)
|
||||
except urllib.error.HTTPError as e:
|
||||
err = json.loads(e.read().decode())
|
||||
print(f"{tok[:18]}... {e.code} {(err.get('errors') or [{}])[0].get('message','')[:50]}")
|
||||
except SystemExit:
|
||||
raise
|
||||
except Exception as ex:
|
||||
print(f"{tok[:18]}... err {ex}")
|
||||
|
||||
print("No token could update tunnel config")
|
||||
178
deploy/delegate-godaddy-to-cf.py
Normal file
178
deploy/delegate-godaddy-to-cf.py
Normal file
@@ -0,0 +1,178 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Delegate exposedgays.com from GoDaddy to Cloudflare.
|
||||
Requires exposedgays.com zone to exist in Cloudflare (dashboard add if API create fails).
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
DOMAIN = "exposedgays.com"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL_CNAME = "03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
GD = "https://api.godaddy.com/v1"
|
||||
GODADDY_KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
GODADDY_SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
|
||||
|
||||
def load_cf_token():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
|
||||
|
||||
def cf(tok, path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
GD + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {GODADDY_KEY}:{GODADDY_SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:800]}
|
||||
|
||||
|
||||
def find_zone(tok):
|
||||
for status in ["active", "pending", ""]:
|
||||
q = f"name={DOMAIN}" + (f"&status={status}" if status else "")
|
||||
res = cf(tok, f"/zones?{q}")
|
||||
if res.get("result"):
|
||||
return res["result"][0]
|
||||
page = 1
|
||||
while page <= 20:
|
||||
res = cf(tok, f"/zones?per_page=50&page={page}")
|
||||
for z in res.get("result", []):
|
||||
if z["name"] == DOMAIN:
|
||||
return z
|
||||
if len(res.get("result", [])) < 50:
|
||||
break
|
||||
page += 1
|
||||
return None
|
||||
|
||||
|
||||
def upsert_tunnel_cnames(tok, zid):
|
||||
records = cf(tok, f"/zones/{zid}/dns_records?per_page=100").get("result", [])
|
||||
for name in [DOMAIN, f"www.{DOMAIN}"]:
|
||||
body = {"type": "CNAME", "name": name, "content": TUNNEL_CNAME, "ttl": 1, "proxied": True}
|
||||
existing = next(
|
||||
(r for r in records if r["name"] == name and r["type"] in ("A", "CNAME", "AAAA")),
|
||||
None,
|
||||
)
|
||||
if existing:
|
||||
res = cf(tok, f"/zones/{zid}/dns_records/{existing['id']}", "PATCH", body)
|
||||
action = "PATCH"
|
||||
else:
|
||||
res = cf(tok, f"/zones/{zid}/dns_records", "POST", body)
|
||||
action = "POST"
|
||||
print(f" CF {action} {name}: success={res.get('success')} errors={res.get('errors')}")
|
||||
|
||||
|
||||
def verify():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
f"dig @1.1.1.1 {DOMAIN} NS +short",
|
||||
f"dig @1.1.1.1 www.{DOMAIN} A +short",
|
||||
f"curl -sSI https://www.{DOMAIN}/ | head -10",
|
||||
f"curl -sSL https://www.{DOMAIN}/ | head -c 200",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n$ {cmd}")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace"))
|
||||
c.close()
|
||||
|
||||
|
||||
def main():
|
||||
tok = load_cf_token()
|
||||
zone = find_zone(tok)
|
||||
|
||||
if not zone:
|
||||
created = cf(
|
||||
tok,
|
||||
"/zones",
|
||||
"POST",
|
||||
{"name": DOMAIN, "account": {"id": ACCOUNT}, "type": "full", "jump_start": False},
|
||||
)
|
||||
if created.get("success"):
|
||||
zone = created["result"]
|
||||
print("Created CF zone via API")
|
||||
else:
|
||||
print("CF zone not found and API create failed:")
|
||||
print(" ", created.get("errors"))
|
||||
print("\nAdd exposedgays.com at https://dash.cloudflare.com then re-run:")
|
||||
print(" python exposedgays/deploy/delegate-godaddy-to-cf.py")
|
||||
return 1
|
||||
|
||||
zid = zone["id"]
|
||||
nameservers = zone.get("name_servers", [])
|
||||
status = zone.get("status")
|
||||
print(f"CF zone {zid} status={status}")
|
||||
print(f"CF nameservers: {nameservers}")
|
||||
|
||||
print("\n=== GoDaddy before ===")
|
||||
code, before = gd(f"/domains/{DOMAIN}")
|
||||
print(f"HTTP {code} NS={before.get('nameServers') if code == 200 else before}")
|
||||
|
||||
print("\n=== Cloudflare tunnel DNS ===")
|
||||
upsert_tunnel_cnames(tok, zid)
|
||||
|
||||
if not nameservers:
|
||||
print("No nameservers on zone — cannot delegate GoDaddy")
|
||||
return 1
|
||||
|
||||
print("\n=== GoDaddy PATCH nameservers -> Cloudflare ===")
|
||||
code, res = gd(f"/domains/{DOMAIN}", "PATCH", {"nameServers": nameservers})
|
||||
print(f"HTTP {code}: {json.dumps(res, indent=2) if res else '{}'}")
|
||||
|
||||
code, after = gd(f"/domains/{DOMAIN}")
|
||||
if code == 200:
|
||||
print(f"\nGoDaddy after NS={after.get('nameServers')}")
|
||||
|
||||
print("\nWaiting 15s for propagation...")
|
||||
time.sleep(15)
|
||||
verify()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
64
deploy/deploy-v2-features.py
Normal file
64
deploy/deploy-v2-features.py
Normal file
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Push v2 features and deploy ExposedGays on Coolify."""
|
||||
import paramiko
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
HOST = "10.10.0.10"
|
||||
USER = "localadministrator"
|
||||
PASS = "Bbt9115xty9176!"
|
||||
GITEA_TOKEN = "5c323b8ea677d78b201341f6833d57156a607008"
|
||||
APP = "ira4bw4nhbvm87o7s3fgqu6v"
|
||||
REPO_DIR = r"C:\Users\Local Administrator\exposedgays"
|
||||
|
||||
def local_git(*args):
|
||||
cmd = ["git", "-C", REPO_DIR] + list(args)
|
||||
print("$", " ".join(cmd))
|
||||
r = subprocess.run(cmd, capture_output=True, text=True)
|
||||
print(r.stdout[-4000:] if r.stdout else "")
|
||||
if r.stderr:
|
||||
print(r.stderr[-2000:])
|
||||
return r.returncode
|
||||
|
||||
# Stage and commit locally
|
||||
local_git("add", "-A")
|
||||
local_git("status", "--short")
|
||||
local_git(
|
||||
"commit", "-m",
|
||||
"v2: ID verification by state, 15 photos/5 albums, 20 quick replies, logo, mobile polish"
|
||||
)
|
||||
local_git("push", "origin", "main")
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(HOST, username=USER, password=PASS, timeout=20, allow_agent=False, look_for_keys=False)
|
||||
|
||||
def run(cmd, t=300):
|
||||
print("$", cmd[:180])
|
||||
_, o, e = c.exec_command(cmd, timeout=t)
|
||||
code = o.channel.recv_exit_status()
|
||||
out = (o.read() + e.read()).decode(errors="replace")
|
||||
print(out[-6000:])
|
||||
print("exit", code, "\n")
|
||||
return code, out
|
||||
|
||||
run("cd /tmp/exposedgays && git pull origin main")
|
||||
run(
|
||||
"TOKEN=$(cat /home/localadministrator/.coolify-token | tail -1); "
|
||||
f"curl -sS -X POST 'http://127.0.0.1:8000/api/v1/deploy?uuid={APP}&force_rebuild=true' "
|
||||
'-H "Authorization: Bearer $TOKEN"'
|
||||
)
|
||||
|
||||
for i in range(120):
|
||||
_, out = run("docker exec coolify php artisan queue:work --queue=high,default --once --timeout=900 2>&1", t=920)
|
||||
if "ApplicationDeploymentJob" in out:
|
||||
if "DONE" in out:
|
||||
print("=== DEPLOY SUCCESS ===")
|
||||
break
|
||||
if "FAIL" in out:
|
||||
print("=== DEPLOY FAILED ===")
|
||||
break
|
||||
|
||||
run("curl -sS -o /dev/null -w '%{http_code}' https://exposedgays.com/ 2>&1 || curl -sS -o /dev/null -w '%{http_code}' http://127.0.0.1:3000/ 2>&1")
|
||||
c.close()
|
||||
print("Done.")
|
||||
26
deploy/diag-tunnel.py
Normal file
26
deploy/diag-tunnel.py
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
|
||||
cmds = [
|
||||
"sudo systemctl is-active cloudflared",
|
||||
"sudo systemctl status cloudflared --no-pager | head -20",
|
||||
"sudo ls -la /etc/cloudflared/",
|
||||
"sudo ls -la /root/.cloudflared/ 2>/dev/null || echo no_root_cloudflared",
|
||||
"sudo ls -la /home/localadministrator/.cloudflared/ 2>/dev/null || echo no_home_cloudflared",
|
||||
"dig @1.1.1.1 www.exposedgays.com A +short",
|
||||
"dig @1.1.1.1 www.onsethost.com CNAME +short",
|
||||
"dig @1.1.1.1 coolify.onsethost.com CNAME +short",
|
||||
"curl -sSI --max-time 15 https://coolify.onsethost.com/ | head -8",
|
||||
"curl -sSI --max-time 15 https://www.exposedgays.com/ 2>&1 | head -8",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=40)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:3000])
|
||||
c.close()
|
||||
23
deploy/dns-trace.py
Normal file
23
deploy/dns-trace.py
Normal file
@@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
|
||||
cmds = [
|
||||
"dig @ns71.domaincontrol.com www.exposedgays.com ANY +noall +answer",
|
||||
"dig @ns71.domaincontrol.com exposedgays.com ANY +noall +answer",
|
||||
"dig +trace www.exposedgays.com A 2>&1 | tail -20",
|
||||
"dig 03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com A +short",
|
||||
"getent hosts www.exposedgays.com",
|
||||
"resolvectl query www.exposedgays.com 2>/dev/null || systemd-resolve --status www.exposedgays.com 2>/dev/null | head -20",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=60)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:4000])
|
||||
|
||||
c.close()
|
||||
93
deploy/final-verify-and-fix.py
Normal file
93
deploy/final-verify-and-fix.py
Normal file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify DNS, attempt tunnel route via API endpoints we can access, restart cloudflared."""
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
ZID = "b9e032992b315331c8b0571473fedc89"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
HOSTS = ["exposedgays.com", "www.exposedgays.com"]
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
|
||||
def cf(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return r.status, json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:300]}
|
||||
|
||||
|
||||
# Ensure DNS records are correct (re-upsert proxied CNAMEs)
|
||||
print("=== CF DNS records ===")
|
||||
code, recs = cf(f"/zones/{ZID}/dns_records?per_page=100")
|
||||
records = recs.get("result", [])
|
||||
for name in HOSTS:
|
||||
body = {
|
||||
"type": "CNAME",
|
||||
"name": name,
|
||||
"content": f"{TUNNEL}.cfargotunnel.com",
|
||||
"ttl": 1,
|
||||
"proxied": True,
|
||||
}
|
||||
existing = next((r for r in records if r["name"] == name and r["type"] in ("A", "CNAME")), None)
|
||||
if existing:
|
||||
code, res = cf(f"/zones/{ZID}/dns_records/{existing['id']}", "PATCH", body)
|
||||
act = "PATCH"
|
||||
else:
|
||||
code, res = cf(f"/zones/{ZID}/dns_records", "POST", body)
|
||||
act = "POST"
|
||||
print(f" {act} {name}: HTTP {code} success={res.get('success')}")
|
||||
|
||||
# Try teamnet hostname route endpoints (some accounts expose these)
|
||||
for path, method, body in [
|
||||
(f"/accounts/{ACCOUNT}/teamnet/routes", "POST", {"network": "exposedgays.com", "tunnel_id": TUNNEL, "comment": "exposedgays apex"}),
|
||||
(f"/accounts/{ACCOUNT}/teamnet/routes", "POST", {"network": "www.exposedgays.com", "tunnel_id": TUNNEL, "comment": "exposedgays www"}),
|
||||
]:
|
||||
code, res = cf(path, method, body)
|
||||
print(f"\n{method} {path}: HTTP {code} success={res.get('success')} errors={res.get('errors')}")
|
||||
|
||||
# Restart cloudflared and verify
|
||||
ssh = paramiko.SSHClient()
|
||||
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
ssh.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
ssh.exec_command(f"echo '{PASS}' | sudo -S systemctl restart cloudflared")
|
||||
time.sleep(12)
|
||||
|
||||
cmds = [
|
||||
"dig @annabel.ns.cloudflare.com exposedgays.com A +short",
|
||||
"dig @annabel.ns.cloudflare.com www.exposedgays.com A +short",
|
||||
"sudo journalctl -u cloudflared --no-pager -n 4 | grep -o 'exposedgays' | head -3 || echo NO_EXPOSEDGAYS_IN_REMOTE",
|
||||
"curl -sSI --max-time 25 https://exposedgays.com/ 2>&1 | head -15",
|
||||
"curl -sSI --max-time 25 https://www.exposedgays.com/ 2>&1 | head -15",
|
||||
"curl -sSL --max-time 25 https://www.exposedgays.com/ 2>&1 | head -c 400",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n$ {cmd}")
|
||||
_, o, e = ssh.exec_command(cmd, timeout=40)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:2500])
|
||||
ssh.close()
|
||||
36
deploy/find-onsethost-cf-env.py
Normal file
36
deploy/find-onsethost-cf-env.py
Normal file
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
|
||||
cmds = [
|
||||
"docker ps --format '{{.Names}}' | grep -i onset | head -10",
|
||||
"docker ps --format '{{.Names}}' | grep -i exposed | head -5",
|
||||
f"echo '{PASS}' | sudo -S cat /etc/infra/secrets.env 2>/dev/null | grep -i cloudflare",
|
||||
"grep -h CLOUDFLARE /home/localadministrator/coder/onsethost/.env.local /home/localadministrator/coder/onsethost/.env 2>/dev/null",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print("===", cmd, "===")
|
||||
_, o, e = c.exec_command(cmd, timeout=60)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:5000])
|
||||
print()
|
||||
|
||||
# Get onsethost container env
|
||||
_, o, e = c.exec_command(
|
||||
"docker ps --format '{{.Names}}' | grep -E 'onsethost|woeslw' | head -3", timeout=30
|
||||
)
|
||||
o.channel.recv_exit_status()
|
||||
names = (o.read() + e.read()).decode().strip().splitlines()
|
||||
for name in names:
|
||||
if not name.strip():
|
||||
continue
|
||||
print(f"=== env {name} CLOUDFLARE ===")
|
||||
_, o2, e2 = c.exec_command(f"docker exec {name.strip()} env 2>/dev/null | grep -i CLOUDFLARE", timeout=30)
|
||||
o2.channel.recv_exit_status()
|
||||
print((o2.read() + e2.read()).decode(errors="replace"))
|
||||
|
||||
c.close()
|
||||
179
deploy/finish-after-cf-zone.py
Normal file
179
deploy/finish-after-cf-zone.py
Normal file
@@ -0,0 +1,179 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Run AFTER adding exposedgays.com in Cloudflare dashboard.
|
||||
Completes: tunnel public hostnames (if token allows), CF DNS CNAMEs, GoDaddy NS delegation.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
DOMAIN = "exposedgays.com"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL_ID = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
TUNNEL_CNAME = f"{TUNNEL_ID}.cfargotunnel.com"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
GD = "https://api.godaddy.com/v1"
|
||||
GODADDY_KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
GODADDY_SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
EG_RULES = [
|
||||
{"hostname": "exposedgays.com", "service": "https://localhost:443", "originRequest": {"noTLSVerify": True}},
|
||||
{"hostname": "www.exposedgays.com", "service": "https://localhost:443", "originRequest": {"noTLSVerify": True}},
|
||||
]
|
||||
|
||||
|
||||
def load_cf_token():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
|
||||
|
||||
def cf(tok, path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
return r.status, json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode())
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
GD + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {GODADDY_KEY}:{GODADDY_SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:500]}
|
||||
|
||||
|
||||
def upsert_tunnel_cnames(tok, zid):
|
||||
records = cf(tok, f"/zones/{zid}/dns_records?per_page=100")[1].get("result", [])
|
||||
for name in [DOMAIN, f"www.{DOMAIN}"]:
|
||||
body = {"type": "CNAME", "name": name, "content": TUNNEL_CNAME, "ttl": 1, "proxied": True}
|
||||
existing = next((r for r in records if r["name"] == name and r["type"] in ("A", "CNAME", "AAAA")), None)
|
||||
if existing:
|
||||
code, res = cf(tok, f"/zones/{zid}/dns_records/{existing['id']}", "PATCH", body)
|
||||
action = "PATCH"
|
||||
else:
|
||||
code, res = cf(tok, f"/zones/{zid}/dns_records", "POST", body)
|
||||
action = "POST"
|
||||
print(f" {action} {name}: HTTP {code} success={res.get('success')} errors={res.get('errors')}")
|
||||
|
||||
|
||||
def try_tunnel_ingress(tok):
|
||||
code, body = cf(tok, f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL_ID}/configurations")
|
||||
if code != 200 or not body.get("result"):
|
||||
print("Tunnel config API blocked — add Public Hostnames manually in Zero Trust dashboard:")
|
||||
print(" exposedgays.com -> https://localhost:443 (No TLS Verify)")
|
||||
print(" www.exposedgays.com -> https://localhost:443 (No TLS Verify)")
|
||||
return False
|
||||
|
||||
ingress = body["result"].get("config", {}).get("ingress", [])
|
||||
hostnames = {i.get("hostname") for i in ingress if i.get("hostname")}
|
||||
changed = False
|
||||
for rule in EG_RULES:
|
||||
if rule["hostname"] not in hostnames:
|
||||
catch = next((i for i, x in enumerate(ingress) if not x.get("hostname")), len(ingress))
|
||||
ingress.insert(catch, rule)
|
||||
changed = True
|
||||
print(" added tunnel ingress", rule["hostname"])
|
||||
|
||||
if not changed:
|
||||
print(" tunnel ingress already has exposedgays hostnames")
|
||||
return True
|
||||
|
||||
put_code, put = cf(
|
||||
tok,
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL_ID}/configurations",
|
||||
"PUT",
|
||||
{"config": {"ingress": ingress, "warp-routing": {"enabled": False}}},
|
||||
)
|
||||
print(f" PUT tunnel config HTTP {put_code} success={put.get('success')} errors={put.get('errors')}")
|
||||
if put.get("success"):
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
c.exec_command(f"echo '{PASS}' | sudo -S systemctl restart cloudflared")
|
||||
c.close()
|
||||
time.sleep(8)
|
||||
return put.get("success", False)
|
||||
|
||||
|
||||
def main():
|
||||
tok = load_cf_token()
|
||||
code, zres = cf(tok, f"/zones?name={DOMAIN}")
|
||||
if not zres.get("result"):
|
||||
print(f"BLOCKED: {DOMAIN} not in Cloudflare yet.")
|
||||
print("Add it at https://dash.cloudflare.com → Add a Site → exposedgays.com (Free plan)")
|
||||
print("Then re-run: python exposedgays/deploy/finish-after-cf-zone.py")
|
||||
return 1
|
||||
|
||||
zone = zres["result"][0]
|
||||
zid = zone["id"]
|
||||
nameservers = zone.get("name_servers", [])
|
||||
print(f"Found zone {zid} status={zone['status']}")
|
||||
print(f"Nameservers: {nameservers}")
|
||||
|
||||
print("\n=== Tunnel ingress ===")
|
||||
try_tunnel_ingress(tok)
|
||||
|
||||
print("\n=== Cloudflare DNS (proxied CNAME -> tunnel) ===")
|
||||
upsert_tunnel_cnames(tok, zid)
|
||||
|
||||
if nameservers:
|
||||
print("\n=== GoDaddy NS -> Cloudflare ===")
|
||||
gcode, gres = gd(f"/domains/{DOMAIN}", "PATCH", {"nameServers": nameservers})
|
||||
print(f"PATCH HTTP {gcode}: {gres}")
|
||||
_, after = gd(f"/domains/{DOMAIN}")
|
||||
print("GoDaddy NS now:", after.get("nameServers"))
|
||||
|
||||
print("\nWaiting 20s...")
|
||||
time.sleep(20)
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for cmd in [
|
||||
f"dig @1.1.1.1 {DOMAIN} A +short",
|
||||
f"dig @1.1.1.1 www.{DOMAIN} A +short",
|
||||
f"curl -sSI --max-time 20 https://{DOMAIN}/ | head -10",
|
||||
f"curl -sSL --max-time 20 https://{DOMAIN}/ | head -c 200",
|
||||
]:
|
||||
print(f"\n$ {cmd}")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace"))
|
||||
c.close()
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
110
deploy/fix-dns-now.py
Normal file
110
deploy/fix-dns-now.py
Normal file
@@ -0,0 +1,110 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Emergency DNS fix: revert GoDaddy NS + point records at tunnel."""
|
||||
import json
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
GODADDY_KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
GODADDY_SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
DOMAIN = "exposedgays.com"
|
||||
TUNNEL_CNAME = "03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com"
|
||||
GD_NS = ["ns71.domaincontrol.com", "ns72.domaincontrol.com"]
|
||||
GD = "https://api.godaddy.com/v1"
|
||||
PASS = "Bbt9115xty9176!"
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
GD + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {GODADDY_KEY}:{GODADDY_SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:800]}
|
||||
|
||||
|
||||
def main():
|
||||
code, before = gd(f"/domains/{DOMAIN}")
|
||||
print("Before NS:", before.get("nameServers") if code == 200 else before)
|
||||
|
||||
print("\n=== Revert registrar NS to GoDaddy ===")
|
||||
code, res = gd(f"/domains/{DOMAIN}", "PATCH", {"nameServers": GD_NS})
|
||||
print(f"PATCH NS HTTP {code}: {res}")
|
||||
|
||||
code, mid = gd(f"/domains/{DOMAIN}")
|
||||
print("After NS revert:", mid.get("nameServers") if code == 200 else mid)
|
||||
|
||||
print("\n=== Set tunnel CNAME for www ===")
|
||||
code, res = gd(
|
||||
f"/domains/{DOMAIN}/records/CNAME/www",
|
||||
"PUT",
|
||||
[{"data": TUNNEL_CNAME, "ttl": 600}],
|
||||
)
|
||||
print(f"PUT www CNAME HTTP {code}: {res}")
|
||||
|
||||
print("\n=== Try apex CNAME (GoDaddy may reject) ===")
|
||||
code, res = gd(
|
||||
f"/domains/{DOMAIN}/records/CNAME/@",
|
||||
"PUT",
|
||||
[{"data": TUNNEL_CNAME, "ttl": 600}],
|
||||
)
|
||||
print(f"PUT @ CNAME HTTP {code}: {res}")
|
||||
|
||||
if code not in (200, 204):
|
||||
print("\n=== Apex CNAME failed — enable domain forward @ -> www ===")
|
||||
code, res = gd(
|
||||
f"/domains/{DOMAIN}/forwarding",
|
||||
"PATCH",
|
||||
{
|
||||
"forwarding": {
|
||||
"type": "PERMANENT",
|
||||
"url": f"https://www.{DOMAIN}",
|
||||
"mask": False,
|
||||
}
|
||||
},
|
||||
)
|
||||
print(f"PATCH forwarding HTTP {code}: {res}")
|
||||
|
||||
print("\n=== Current GoDaddy records ===")
|
||||
code, records = gd(f"/domains/{DOMAIN}/records")
|
||||
for r in records if isinstance(records, list) else []:
|
||||
print(f" {r['type']:5} {r['name']:20} -> {r.get('data')}")
|
||||
|
||||
print("\nWaiting 20s for propagation...")
|
||||
time.sleep(20)
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for cmd in [
|
||||
f"dig @1.1.1.1 {DOMAIN} NS +short",
|
||||
f"dig @1.1.1.1 {DOMAIN} A +short",
|
||||
f"dig @1.1.1.1 www.{DOMAIN} CNAME +short",
|
||||
f"curl -sSI https://www.{DOMAIN}/ | head -12",
|
||||
f"curl -sSL https://www.{DOMAIN}/ | head -c 300",
|
||||
f"curl -sSI https://{DOMAIN}/ | head -12",
|
||||
]:
|
||||
print(f"\n$ {cmd}")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace"))
|
||||
c.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
97
deploy/fix-exposedgays-dns-direct.py
Normal file
97
deploy/fix-exposedgays-dns-direct.py
Normal file
@@ -0,0 +1,97 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Fix exposedgays.com 404: route via direct A record like thepinkpulse.com."""
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ZID = "b9e032992b315331c8b0571473fedc89"
|
||||
ORIGIN_IP = "68.248.192.234"
|
||||
HOSTS = ["exposedgays.com", "www.exposedgays.com"]
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
|
||||
def get_token():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
|
||||
|
||||
def cf(token, path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return r.status, json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode())
|
||||
|
||||
|
||||
def ssh_check():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"curl -sSI --max-time 20 https://exposedgays.com/ | head -12",
|
||||
"curl -sSI --max-time 20 https://www.exposedgays.com/ | head -12",
|
||||
"curl -sSL --max-time 20 https://exposedgays.com/ | head -c 200",
|
||||
]
|
||||
out = []
|
||||
for cmd in cmds:
|
||||
_, o, e = c.exec_command(cmd, timeout=35)
|
||||
o.channel.recv_exit_status()
|
||||
out.append(f"$ {cmd}\n" + (o.read() + e.read()).decode(errors="replace")[:1500])
|
||||
c.close()
|
||||
return "\n\n".join(out)
|
||||
|
||||
|
||||
def main():
|
||||
token = get_token()
|
||||
code, recs_body = cf(token, f"/zones/{ZID}/dns_records?per_page=100")
|
||||
if not recs_body.get("success"):
|
||||
raise SystemExit(f"list records failed: {recs_body.get('errors')}")
|
||||
records = recs_body["result"]
|
||||
|
||||
for name in HOSTS:
|
||||
body = {"type": "A", "name": name, "content": ORIGIN_IP, "ttl": 1, "proxied": True}
|
||||
existing = next(
|
||||
(r for r in records if r["name"] == name and r["type"] in ("A", "CNAME", "AAAA")),
|
||||
None,
|
||||
)
|
||||
if existing:
|
||||
code, res = cf(token, f"/zones/{ZID}/dns_records/{existing['id']}", "PATCH", body)
|
||||
action = "PATCH"
|
||||
else:
|
||||
code, res = cf(token, f"/zones/{ZID}/dns_records", "POST", body)
|
||||
action = "POST"
|
||||
print(f"{action} {name}: HTTP {code} success={res.get('success')} errors={res.get('errors')}")
|
||||
if not res.get("success"):
|
||||
raise SystemExit(1)
|
||||
|
||||
print("\nFinal apex/www records:")
|
||||
_, final_body = cf(token, f"/zones/{ZID}/dns_records?per_page=100")
|
||||
for r in final_body.get("result", []):
|
||||
if r["name"] in HOSTS:
|
||||
print(f" {r['type']} {r['name']} -> {r['content']} proxied={r.get('proxied')}")
|
||||
|
||||
print("\nWaiting 15s for DNS/proxy propagation...")
|
||||
time.sleep(15)
|
||||
print("\n=== verify ===")
|
||||
print(ssh_check())
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
117
deploy/fix-tunnel-ingress.py
Normal file
117
deploy/fix-tunnel-ingress.py
Normal file
@@ -0,0 +1,117 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add exposedgays.com to Cloudflare tunnel remote ingress + verify DNS."""
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL_ID = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
HOSTS = ["exposedgays.com", "www.exposedgays.com"]
|
||||
RULES = [
|
||||
{
|
||||
"hostname": "exposedgays.com",
|
||||
"service": "https://localhost:443",
|
||||
"originRequest": {"noTLSVerify": True},
|
||||
},
|
||||
{
|
||||
"hostname": "www.exposedgays.com",
|
||||
"service": "https://localhost:443",
|
||||
"originRequest": {"noTLSVerify": True},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
def get_token():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
|
||||
|
||||
def cf(token, path, method="GET", data=None):
|
||||
req = urllib.request.Request(
|
||||
f"https://api.cloudflare.com/client/v4{path}",
|
||||
data=json.dumps(data).encode() if data is not None else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=60) as r:
|
||||
return r.status, json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return e.code, json.loads(e.read().decode())
|
||||
|
||||
|
||||
def ssh_run(cmd, timeout=90):
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
_, o, e = c.exec_command(cmd, timeout=timeout)
|
||||
o.channel.recv_exit_status()
|
||||
out = (o.read() + e.read()).decode(errors="replace")
|
||||
c.close()
|
||||
return out
|
||||
|
||||
|
||||
def merge_ingress(ingress):
|
||||
hostnames = {i.get("hostname") for i in ingress if i.get("hostname")}
|
||||
changed = False
|
||||
for rule in RULES:
|
||||
if rule["hostname"] not in hostnames:
|
||||
catch = next((i for i, x in enumerate(ingress) if not x.get("hostname")), len(ingress))
|
||||
ingress.insert(catch, rule)
|
||||
hostnames.add(rule["hostname"])
|
||||
changed = True
|
||||
print("added", rule["hostname"])
|
||||
return ingress, changed
|
||||
|
||||
|
||||
def main():
|
||||
token = get_token()
|
||||
|
||||
code, body = cf(token, f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL_ID}/configurations")
|
||||
print(f"GET tunnel config HTTP {code}")
|
||||
if not body.get("result"):
|
||||
print(body.get("errors"))
|
||||
return 1
|
||||
|
||||
ingress = body["result"].get("config", {}).get("ingress", [])
|
||||
present = [i.get("hostname") for i in ingress if i.get("hostname") in HOSTS]
|
||||
print("exposedgays in remote ingress:", present)
|
||||
|
||||
ingress, changed = merge_ingress(ingress)
|
||||
if changed:
|
||||
put_code, put = cf(
|
||||
token,
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL_ID}/configurations",
|
||||
"PUT",
|
||||
{"config": {"ingress": ingress, "warp-routing": {"enabled": False}}},
|
||||
)
|
||||
print(f"PUT HTTP {put_code} success={put.get('success')} errors={put.get('errors')}")
|
||||
if not put.get("success"):
|
||||
return 1
|
||||
print(ssh_run(f"echo '{PASS}' | sudo -S systemctl restart cloudflared"))
|
||||
time.sleep(8)
|
||||
|
||||
print("\n=== verify ===")
|
||||
print(
|
||||
ssh_run(
|
||||
"systemctl is-active cloudflared; "
|
||||
"dig @1.1.1.1 www.exposedgays.com CNAME +short; "
|
||||
"curl -sSI --max-time 20 https://www.exposedgays.com/ | head -12; "
|
||||
"curl -sSL --max-time 20 https://www.exposedgays.com/ | head -c 250"
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
55
deploy/force-godaddy-ns-cf.py
Normal file
55
deploy/force-godaddy-ns-cf.py
Normal file
@@ -0,0 +1,55 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Force GoDaddy registrar NS to Cloudflare."""
|
||||
import json
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
DOMAIN = "exposedgays.com"
|
||||
CF_NS = ["annabel.ns.cloudflare.com", "mitchell.ns.cloudflare.com"]
|
||||
GD = "https://api.godaddy.com/v1"
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
GD + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {KEY}:{SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:500]}
|
||||
|
||||
|
||||
code, d = gd(f"/domains/{DOMAIN}")
|
||||
print("Before:", d.get("nameServers"))
|
||||
|
||||
# Try PUT instead of PATCH
|
||||
code, res = gd(f"/domains/{DOMAIN}", "PATCH", {"nameServers": CF_NS, "locked": False})
|
||||
print(f"PATCH HTTP {code}: {res}")
|
||||
|
||||
time.sleep(3)
|
||||
code, d = gd(f"/domains/{DOMAIN}")
|
||||
print("After:", d.get("nameServers"))
|
||||
|
||||
# Also try nameservers endpoint if exists
|
||||
code, res = gd(f"/domains/{DOMAIN}/nameservers", "PUT", CF_NS)
|
||||
print(f"PUT /nameservers HTTP {code}: {res}")
|
||||
|
||||
time.sleep(3)
|
||||
code, d = gd(f"/domains/{DOMAIN}")
|
||||
print("Final:", d.get("nameServers"))
|
||||
@@ -5,7 +5,13 @@ c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password="Bbt9115xty9176!", timeout=20)
|
||||
_, o, e = c.exec_command(
|
||||
"docker exec coolify-db psql -U coolify -d coolify -t -A -c "
|
||||
"\"SELECT logs FROM application_deployment_queues WHERE id=1086;\""
|
||||
"\"SELECT logs FROM application_deployment_queues WHERE id=1088;\""
|
||||
)
|
||||
print(o.read().decode()[-15000:])
|
||||
text = o.read().decode()
|
||||
# find error lines
|
||||
for needle in ["Type error", "Failed to compile", "ERROR:", "error"]:
|
||||
idx = text.rfind(needle)
|
||||
if idx >= 0:
|
||||
print(text[max(0, idx-500):idx+1500])
|
||||
print("---")
|
||||
c.close()
|
||||
149
deploy/godaddy-delegate-now.py
Normal file
149
deploy/godaddy-delegate-now.py
Normal file
@@ -0,0 +1,149 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Point GoDaddy NS at Cloudflare (account default NS) + set CF DNS when zone exists."""
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
DOMAIN = "exposedgays.com"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL_CNAME = "03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com"
|
||||
# Same NS pair used by justtworoommates.com, thepinkpulse.com, freerangefruit.com on this CF account
|
||||
CF_NS = ["annabel.ns.cloudflare.com", "mitchell.ns.cloudflare.com"]
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
GD = "https://api.godaddy.com/v1"
|
||||
GODADDY_KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
GODADDY_SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
|
||||
|
||||
def load_cf_token():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
return re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
|
||||
|
||||
def cf(tok, path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
GD + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {GODADDY_KEY}:{GODADDY_SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:800]}
|
||||
|
||||
|
||||
def find_zone(tok):
|
||||
res = cf(tok, f"/zones?name={DOMAIN}")
|
||||
return res["result"][0] if res.get("result") else None
|
||||
|
||||
|
||||
def upsert_tunnel_cnames(tok, zid):
|
||||
records = cf(tok, f"/zones/{zid}/dns_records?per_page=100").get("result", [])
|
||||
for name in [DOMAIN, f"www.{DOMAIN}"]:
|
||||
body = {"type": "CNAME", "name": name, "content": TUNNEL_CNAME, "ttl": 1, "proxied": True}
|
||||
existing = next(
|
||||
(r for r in records if r["name"] == name and r["type"] in ("A", "CNAME", "AAAA")),
|
||||
None,
|
||||
)
|
||||
if existing:
|
||||
res = cf(tok, f"/zones/{zid}/dns_records/{existing['id']}", "PATCH", body)
|
||||
action = "PATCH"
|
||||
else:
|
||||
res = cf(tok, f"/zones/{zid}/dns_records", "POST", body)
|
||||
action = "POST"
|
||||
print(f" CF {action} {name}: success={res.get('success')} errors={res.get('errors')}")
|
||||
|
||||
|
||||
def verify():
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for cmd in [
|
||||
f"dig @1.1.1.1 {DOMAIN} NS +short",
|
||||
f"dig @1.1.1.1 www.{DOMAIN} A +short",
|
||||
f"curl -sSI https://www.{DOMAIN}/ | head -8",
|
||||
f"curl -sSL https://www.{DOMAIN}/ | head -c 200",
|
||||
]:
|
||||
print(f"\n$ {cmd}")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace"))
|
||||
c.close()
|
||||
|
||||
|
||||
def main():
|
||||
print("=== GoDaddy before ===")
|
||||
code, before = gd(f"/domains/{DOMAIN}")
|
||||
print(f"HTTP {code} NS={before.get('nameServers') if code == 200 else before}")
|
||||
|
||||
print(f"\n=== GoDaddy PATCH NS -> {CF_NS} ===")
|
||||
code, res = gd(f"/domains/{DOMAIN}", "PATCH", {"nameServers": CF_NS})
|
||||
print(f"HTTP {code}: {json.dumps(res, indent=2) if res else '{}'}")
|
||||
|
||||
code, after = gd(f"/domains/{DOMAIN}")
|
||||
if code == 200:
|
||||
print(f"GoDaddy after NS={after.get('nameServers')}")
|
||||
|
||||
tok = load_cf_token()
|
||||
zone = find_zone(tok)
|
||||
if not zone:
|
||||
created = cf(
|
||||
tok,
|
||||
"/zones",
|
||||
"POST",
|
||||
{"name": DOMAIN, "account": {"id": ACCOUNT}, "type": "full", "jump_start": False},
|
||||
)
|
||||
if created.get("success"):
|
||||
zone = created["result"]
|
||||
print("\nCreated CF zone via API")
|
||||
else:
|
||||
print("\nCF zone not in account yet (API create still denied).")
|
||||
print("Add exposedgays.com at https://dash.cloudflare.com — NS already points at CF.")
|
||||
print("Then re-run: python exposedgays/deploy/delegate-godaddy-to-cf.py")
|
||||
else:
|
||||
print(f"\nCF zone found: {zone['id']} status={zone['status']}")
|
||||
print("Setting tunnel CNAME records...")
|
||||
upsert_tunnel_cnames(tok, zone["id"])
|
||||
|
||||
print("\nWaiting 20s...")
|
||||
time.sleep(20)
|
||||
verify()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
114
deploy/godaddy-dns-tunnel.py
Normal file
114
deploy/godaddy-dns-tunnel.py
Normal file
@@ -0,0 +1,114 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Point exposedgays.com DNS at Cloudflare tunnel via GoDaddy API."""
|
||||
import json
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
GODADDY_KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
GODADDY_SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
DOMAIN = "exposedgays.com"
|
||||
TUNNEL_CNAME = "03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com"
|
||||
GD = "https://api.godaddy.com/v1"
|
||||
PASS = "Bbt9115xty9176!"
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
GD + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {GODADDY_KEY}:{GODADDY_SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:800]}
|
||||
|
||||
|
||||
def show_records(label):
|
||||
print(f"\n=== {label} ===")
|
||||
code, records = gd(f"/domains/{DOMAIN}/records")
|
||||
print("HTTP", code)
|
||||
for r in records if isinstance(records, list) else []:
|
||||
print(f" {r['type']:5} {r['name']:20} -> {r.get('data')}")
|
||||
|
||||
|
||||
def main():
|
||||
show_records("Before")
|
||||
|
||||
# Full replace must retain NS records.
|
||||
new_records = [
|
||||
{"type": "NS", "name": "@", "data": "ns71.domaincontrol.com", "ttl": 3600},
|
||||
{"type": "NS", "name": "@", "data": "ns72.domaincontrol.com", "ttl": 3600},
|
||||
{"type": "CNAME", "name": "www", "data": TUNNEL_CNAME, "ttl": 600},
|
||||
{"type": "CNAME", "name": "@", "data": TUNNEL_CNAME, "ttl": 600},
|
||||
{
|
||||
"type": "TXT",
|
||||
"name": "_dmarc",
|
||||
"data": "v=DMARC1; p=quarantine; adkim=r; aspf=r; rua=mailto:dmarc_rua@onsecureserver.net;",
|
||||
"ttl": 3600,
|
||||
},
|
||||
]
|
||||
|
||||
print("\n=== PUT full record set ===")
|
||||
code, res = gd(f"/domains/{DOMAIN}/records", "PUT", new_records)
|
||||
print("HTTP", code, res)
|
||||
|
||||
if code not in (200, 204):
|
||||
print("\n=== Per-record fallback ===")
|
||||
# Delete parked apex A
|
||||
code_del, res_del = gd(f"/domains/{DOMAIN}/records/A/@", "DELETE")
|
||||
print("DELETE A @", code_del, res_del)
|
||||
|
||||
# Upsert www CNAME
|
||||
code_www, res_www = gd(
|
||||
f"/domains/{DOMAIN}/records/CNAME/www",
|
||||
"PUT",
|
||||
[{"data": TUNNEL_CNAME, "ttl": 600}],
|
||||
)
|
||||
print("PUT CNAME www", code_www, res_www)
|
||||
|
||||
# Try apex CNAME
|
||||
code_apex, res_apex = gd(
|
||||
f"/domains/{DOMAIN}/records/CNAME/@",
|
||||
"PUT",
|
||||
[{"data": TUNNEL_CNAME, "ttl": 600}],
|
||||
)
|
||||
print("PUT CNAME @", code_apex, res_apex)
|
||||
|
||||
show_records("After")
|
||||
time.sleep(8)
|
||||
|
||||
print("\n=== Verify from COOLIFY_01 ===")
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
f"dig @1.1.1.1 {DOMAIN} CNAME +short",
|
||||
f"dig @1.1.1.1 www.{DOMAIN} CNAME +short",
|
||||
f"curl -sSI https://www.{DOMAIN}/ | head -12",
|
||||
f"curl -sSL https://www.{DOMAIN}/ | head -c 400",
|
||||
f"curl -sSI https://{DOMAIN}/ | head -12",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n$ {cmd}")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace"))
|
||||
c.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
42
deploy/godaddy-exposedgays.py
Normal file
42
deploy/godaddy-exposedgays.py
Normal file
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Check GoDaddy domain status for exposedgays.com."""
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
DOMAIN = "exposedgays.com"
|
||||
BASE = "https://api.godaddy.com/v1"
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
BASE + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {KEY}:{SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return r.status, json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
try:
|
||||
return e.code, json.loads(e.read().decode())
|
||||
except Exception:
|
||||
return e.code, {"raw": e.read().decode()[:500]}
|
||||
|
||||
|
||||
for path in [
|
||||
f"/domains/{DOMAIN}",
|
||||
f"/domains/{DOMAIN}/records",
|
||||
f"/domains/{DOMAIN}/records/A",
|
||||
f"/domains/{DOMAIN}/records/CNAME",
|
||||
]:
|
||||
code, res = gd(path)
|
||||
print(f"\n{path} -> HTTP {code}")
|
||||
print(json.dumps(res, indent=2)[:3000])
|
||||
75
deploy/godaddy-ns-patch.py
Normal file
75
deploy/godaddy-ns-patch.py
Normal file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python3
|
||||
"""PATCH GoDaddy nameservers with consent payload."""
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import datetime, timezone
|
||||
|
||||
KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
DOMAIN = "exposedgays.com"
|
||||
CF_NS = ["annabel.ns.cloudflare.com", "mitchell.ns.cloudflare.com"]
|
||||
GD = "https://api.godaddy.com/v1"
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
GD + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {KEY}:{SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:1000]}
|
||||
|
||||
|
||||
def show(label):
|
||||
code, d = gd(f"/domains/{DOMAIN}")
|
||||
print(f"{label}: HTTP {code} NS={d.get('nameServers') if code == 200 else d}")
|
||||
|
||||
|
||||
show("Before")
|
||||
|
||||
bodies = [
|
||||
{"nameServers": CF_NS},
|
||||
{
|
||||
"nameServers": CF_NS,
|
||||
"consent": {
|
||||
"agreedAt": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z"),
|
||||
"agreedBy": "96.94.95.105",
|
||||
"agreementKeys": ["DNRA"],
|
||||
},
|
||||
},
|
||||
{
|
||||
"nameServers": CF_NS,
|
||||
"locked": False,
|
||||
"consent": {
|
||||
"agreedAt": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z"),
|
||||
"agreedBy": "0.0.0.0",
|
||||
"agreementKeys": ["DNRA", "DNPA"],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
for i, body in enumerate(bodies, 1):
|
||||
print(f"\n=== PATCH attempt {i} ===")
|
||||
print(json.dumps(body, indent=2))
|
||||
code, res = gd(f"/domains/{DOMAIN}", "PATCH", body)
|
||||
print(f"HTTP {code}: {json.dumps(res, indent=2) if res else '{}'}")
|
||||
|
||||
show("After immediate")
|
||||
import time
|
||||
time.sleep(5)
|
||||
show("After 5s")
|
||||
62
deploy/godaddy-ns-to-cf.py
Normal file
62
deploy/godaddy-ns-to-cf.py
Normal file
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Delegate exposedgays.com from GoDaddy to Cloudflare nameservers."""
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
GODADDY_KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
GODADDY_SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
DOMAIN = "exposedgays.com"
|
||||
GD = "https://api.godaddy.com/v1"
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
GD + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {GODADDY_KEY}:{GODADDY_SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:800]}
|
||||
|
||||
|
||||
def main():
|
||||
code, domain = gd(f"/domains/{DOMAIN}")
|
||||
print(f"Domain GET HTTP {code}")
|
||||
if code == 200:
|
||||
print(f" Current NS: {domain.get('nameServers')}")
|
||||
print(f" Status: {domain.get('status')}")
|
||||
|
||||
# Show what we'd set — caller passes NS via env or args in wrapper
|
||||
import sys
|
||||
if len(sys.argv) < 2:
|
||||
print("\nUsage: godaddy-ns-to-cf.py <ns1> <ns2> [ns3...]")
|
||||
return 1
|
||||
|
||||
nameservers = sys.argv[1:]
|
||||
print(f"\nPATCH nameservers -> {nameservers}")
|
||||
code, res = gd(f"/domains/{DOMAIN}", "PATCH", {"nameServers": nameservers})
|
||||
print(f"HTTP {code}: {json.dumps(res, indent=2)}")
|
||||
|
||||
code, domain = gd(f"/domains/{DOMAIN}")
|
||||
if code == 200:
|
||||
print(f"\nAfter PATCH NS: {domain.get('nameServers')}")
|
||||
return 0 if code == 200 else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
45
deploy/godaddy-probe-connect.py
Normal file
45
deploy/godaddy-probe-connect.py
Normal file
@@ -0,0 +1,45 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
|
||||
KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
DOMAIN = "exposedgays.com"
|
||||
BASE = "https://api.godaddy.com/v1"
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
BASE + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {KEY}:{SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:500]}
|
||||
|
||||
|
||||
paths = [
|
||||
f"/domains/{DOMAIN}/connect",
|
||||
f"/domains/{DOMAIN}/connect/cloudflare",
|
||||
f"/domains/{DOMAIN}/suggest",
|
||||
f"/domains/{DOMAIN}/records/NS/@",
|
||||
]
|
||||
for p in paths:
|
||||
code, res = gd(p)
|
||||
print(f"{p} -> {code}")
|
||||
print(json.dumps(res, indent=2)[:800])
|
||||
print()
|
||||
31
deploy/grep-remote-config.py
Normal file
31
deploy/grep-remote-config.py
Normal file
@@ -0,0 +1,31 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
_, o, e = c.exec_command(
|
||||
"sudo journalctl -u cloudflared --no-pager -n 50 | grep 'Updated to new configuration' | tail -1",
|
||||
timeout=30,
|
||||
)
|
||||
o.channel.recv_exit_status()
|
||||
line = (o.read() + e.read()).decode()
|
||||
print("exposedgays in remote:", "exposedgays" in line)
|
||||
for h in ["exposedgays.com", "www.exposedgays.com", "thepinkpulse.com", "avbeat.com"]:
|
||||
print(f" {h}: {h in line}")
|
||||
# restart to pick up dashboard changes
|
||||
_, o2, e2 = c.exec_command(f"echo '{PASS}' | sudo -S systemctl restart cloudflared && sleep 8 && sudo journalctl -u cloudflared --no-pager -n 8", timeout=60)
|
||||
o2.channel.recv_exit_status()
|
||||
out = (o2.read() + e2.read()).decode()
|
||||
print("\nAfter restart:")
|
||||
print("exposedgays:", "exposedgays" in out)
|
||||
for cmd in [
|
||||
"curl -sSI --max-time 25 https://exposedgays.com/ 2>&1 | head -15",
|
||||
"curl -sSI --max-time 25 https://www.exposedgays.com/ 2>&1 | head -15",
|
||||
"curl -sSL --max-time 25 https://www.exposedgays.com/ 2>&1 | head -c 350",
|
||||
]:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=35)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:2000])
|
||||
c.close()
|
||||
25
deploy/list-cf-zones.py
Normal file
25
deploy/list-cf-zones.py
Normal file
@@ -0,0 +1,25 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read() + e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
req = urllib.request.Request(
|
||||
"https://api.cloudflare.com/client/v4/zones?per_page=50",
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
zones = json.load(r)["result"]
|
||||
|
||||
for z in sorted(zones, key=lambda x: x["name"]):
|
||||
mark = " <--" if "exposed" in z["name"] or "gay" in z["name"] else ""
|
||||
print(f"{z['name']:35} {z['status']:10} {z['id']}{mark}")
|
||||
44
deploy/move-to-localhost.py
Normal file
44
deploy/move-to-localhost.py
Normal file
@@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
HOST = "10.10.0.10"
|
||||
USER = "localadministrator"
|
||||
PASS = "Bbt9115xty9176!"
|
||||
APP = "ira4bw4nhbvm87o7s3fgqu6v"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(HOST, username=USER, password=PASS, timeout=20, allow_agent=False, look_for_keys=False)
|
||||
|
||||
|
||||
def run(cmd, t=300):
|
||||
print("$", cmd[:150])
|
||||
_, o, e = c.exec_command(cmd, timeout=t)
|
||||
code = o.channel.recv_exit_status()
|
||||
out = (o.read() + e.read()).decode(errors="replace")
|
||||
print(out[-6000:])
|
||||
print("exit", code, "\n")
|
||||
return code, out
|
||||
|
||||
|
||||
# Move to localhost destination (id=0)
|
||||
run(
|
||||
"docker exec coolify-db psql -U coolify -d coolify -c "
|
||||
"\"UPDATE applications SET destination_id=0 WHERE uuid='ira4bw4nhbvm87o7s3fgqu6v'; "
|
||||
"SELECT destination_id,fqdn FROM applications WHERE uuid='ira4bw4nhbvm87o7s3fgqu6v';\""
|
||||
)
|
||||
|
||||
run(
|
||||
"TOKEN=$(cat /home/localadministrator/.coolify-token | tail -1); "
|
||||
f"curl -sS -X POST 'http://127.0.0.1:8000/api/v1/deploy?uuid={APP}&force_rebuild=true' "
|
||||
'-H "Authorization: Bearer $TOKEN"'
|
||||
)
|
||||
|
||||
for i in range(120):
|
||||
_, out = run("docker exec coolify php artisan queue:work --queue=high,default --once --timeout=900 2>&1", t=920)
|
||||
if "ApplicationDeploymentJob" in out and ("DONE" in out or "FAIL" in out):
|
||||
break
|
||||
|
||||
run("docker ps --format '{{.Names}} {{.Status}}' | grep ira4 || true")
|
||||
run("curl -sS -o /dev/null -w '%{http_code}' -k -H 'Host: exposedgays.com' https://127.0.0.1/")
|
||||
c.close()
|
||||
73
deploy/probe-all-cf-tokens.py
Normal file
73
deploy/probe-all-cf-tokens.py
Normal file
@@ -0,0 +1,73 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
tokens = {}
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
tokens["pfsense"] = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text).group(1)
|
||||
c.close()
|
||||
|
||||
c2 = paramiko.SSHClient()
|
||||
c2.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c2.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for path in [
|
||||
"/home/localadministrator/coder/onsethost/.env.local",
|
||||
"/etc/infra/mail-watchdog.env",
|
||||
"/etc/infra/secrets.env",
|
||||
]:
|
||||
_, o, e = c2.exec_command(
|
||||
f"echo '{PASS}' | sudo -S grep -h CLOUDFLARE_API_TOKEN {path} 2>/dev/null | head -1",
|
||||
timeout=20,
|
||||
)
|
||||
o.channel.recv_exit_status()
|
||||
line = (o.read() + e.read()).decode().strip()
|
||||
if "CLOUDFLARE_API_TOKEN=" in line:
|
||||
tokens[path] = line.split("=", 1)[1].strip().strip('"')
|
||||
c2.close()
|
||||
|
||||
tokens["hardcoded_mail"] = "cfut_zlUt5lAKVsu7qIcRCHJnyhAvpJVF5jx24HlrIEn9a8fcd6a1"
|
||||
|
||||
|
||||
def probe(label, tok):
|
||||
results = {}
|
||||
for name, path, method in [
|
||||
("zone_list", "/zones?per_page=1", "GET"),
|
||||
("zone_create", "/zones", "POST"),
|
||||
("tunnel_cfg", f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations", "GET"),
|
||||
]:
|
||||
body = None
|
||||
if name == "zone_create":
|
||||
body = {"name": "exposedgays.com", "account": {"id": ACCOUNT}, "type": "full"}
|
||||
req = urllib.request.Request(
|
||||
CF + path,
|
||||
data=json.dumps(body).encode() if body else None,
|
||||
method=method if name != "zone_list" else "GET",
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
data = json.load(r)
|
||||
results[name] = ("OK", data.get("success"), len(data.get("result") or []))
|
||||
except urllib.error.HTTPError as e:
|
||||
err = json.loads(e.read().decode())
|
||||
results[name] = (e.code, err.get("errors", [{}])[0].get("message", "")[:80])
|
||||
print(label, results)
|
||||
|
||||
|
||||
for label, tok in tokens.items():
|
||||
if tok:
|
||||
probe(label, tok)
|
||||
10
deploy/read-cf-config.py
Normal file
10
deploy/read-cf-config.py
Normal file
@@ -0,0 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
_, o, e = c.exec_command("sudo cat /etc/cloudflared/config.yml", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode())
|
||||
c.close()
|
||||
16
deploy/read-cloudflared-service.py
Normal file
16
deploy/read-cloudflared-service.py
Normal file
@@ -0,0 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for cmd in [
|
||||
"sudo cat /etc/systemd/system/cloudflared.service",
|
||||
"cloudflared --version",
|
||||
"cloudflared tunnel run --help 2>&1 | head -40",
|
||||
]:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:4000])
|
||||
c.close()
|
||||
9
deploy/read-tunnel.py
Normal file
9
deploy/read-tunnel.py
Normal file
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password="Bbt9115xty9176!", timeout=20)
|
||||
_, o, e = c.exec_command("sudo head -30 /etc/cloudflared/config.yml; echo '---'; sudo cat /etc/cloudflared/*.json 2>/dev/null | head -5; ls -la /etc/cloudflared/ 2>/dev/null", timeout=30)
|
||||
print(o.read().decode())
|
||||
print(e.read().decode())
|
||||
c.close()
|
||||
@@ -1,16 +1,10 @@
|
||||
#!/usr/bin/env python3
|
||||
import io
|
||||
import os
|
||||
import tarfile
|
||||
import paramiko
|
||||
|
||||
HOST = "10.10.0.10"
|
||||
USER = "localadministrator"
|
||||
PASS = "Bbt9115xty9176!"
|
||||
GITEA_TOKEN = "5c323b8ea677d78b201341f6833d57156a607008"
|
||||
APP = "ira4bw4nhbvm87o7s3fgqu6v"
|
||||
PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
GIT_AUTH = f"https://{USER}:{GITEA_TOKEN}@gitea.onsethost.com/localadministrator/exposedgays.git"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
@@ -22,58 +16,38 @@ def run(cmd, t=300):
|
||||
_, o, e = c.exec_command(cmd, timeout=t)
|
||||
code = o.channel.recv_exit_status()
|
||||
out = (o.read() + e.read()).decode(errors="replace")
|
||||
print(out[-10000:])
|
||||
print(out[-5000:])
|
||||
print("exit", code, "\n")
|
||||
return code, out
|
||||
|
||||
|
||||
# Upload updated tarball
|
||||
buf = io.BytesIO()
|
||||
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
|
||||
for root, dirs, files in os.walk(PROJECT_ROOT):
|
||||
dirs[:] = [d for d in dirs if d not in (".next", "node_modules", ".git")]
|
||||
for f in files:
|
||||
if f in (".env.local",):
|
||||
continue
|
||||
full = os.path.join(root, f)
|
||||
arc = os.path.relpath(full, PROJECT_ROOT)
|
||||
tar.add(full, arcname=arc)
|
||||
buf.seek(0)
|
||||
sftp = c.open_sftp()
|
||||
sftp.putfo(buf, "/tmp/exposedgays.tar.gz")
|
||||
sftp.close()
|
||||
|
||||
run("rm -rf /tmp/exposedgays && mkdir -p /tmp/exposedgays && tar -xzf /tmp/exposedgays.tar.gz -C /tmp/exposedgays")
|
||||
run(
|
||||
"cd /tmp/exposedgays && git add -A && "
|
||||
"git commit -m 'Fix Dockerfile: npm install fallback when no lockfile' || true && "
|
||||
f"git push origin main"
|
||||
)
|
||||
|
||||
# Get latest deployment log
|
||||
run(
|
||||
f"TOKEN=$(cat /home/localadministrator/.coolify-token | tail -1); "
|
||||
f"curl -sS 'http://127.0.0.1:8000/api/v1/applications/{APP}/deployments' "
|
||||
f'-H "Authorization: Bearer $TOKEN" | python3 -c "import sys,json;d=json.load(sys.stdin); '
|
||||
f'items=d if isinstance(d,list) else d.get(\\\"deployments\\\",d); '
|
||||
f'[print(x.get(\\\"deployment_uuid\\\"),x.get(\\\"status\\\"),x.get(\\\"failure_reason\\\")) for x in items[:3]]" 2>/dev/null'
|
||||
)
|
||||
|
||||
run(
|
||||
"TOKEN=$(cat /home/localadministrator/.coolify-token | tail -1); "
|
||||
f"curl -sS -X POST 'http://127.0.0.1:8000/api/v1/deploy?uuid={APP}&force_rebuild=true' "
|
||||
'-H "Authorization: Bearer $TOKEN"'
|
||||
)
|
||||
|
||||
for i in range(30):
|
||||
success = False
|
||||
for i in range(200):
|
||||
_, out = run("docker exec coolify php artisan queue:work --queue=high,default --once --timeout=900 2>&1", t=920)
|
||||
if "ApplicationDeploymentJob" in out and "DONE" in out:
|
||||
print("DEPLOY DONE")
|
||||
break
|
||||
if "ApplicationDeploymentJob" in out and "FAIL" in out:
|
||||
print("DEPLOY FAIL - fetching logs")
|
||||
break
|
||||
if "ApplicationDeploymentJob" in out:
|
||||
if "DONE" in out:
|
||||
success = True
|
||||
print("=== DEPLOY SUCCESS ===")
|
||||
break
|
||||
if "FAIL" in out:
|
||||
print("=== DEPLOY FAILED ===")
|
||||
break
|
||||
|
||||
run("docker ps --format '{{.Names}} {{.Status}}' | grep -i ira4 || docker ps --format '{{.Names}} {{.Status}}' | grep -i exposed || true")
|
||||
run(
|
||||
"docker exec coolify-db psql -U coolify -d coolify -c "
|
||||
"\"SELECT id,status FROM application_deployment_queues WHERE application_id='31' ORDER BY id DESC LIMIT 1;\""
|
||||
)
|
||||
run(
|
||||
"docker exec coolify-db psql -U coolify -d coolify -c "
|
||||
"\"SELECT status,name FROM applications WHERE uuid='ira4bw4nhbvm87o7s3fgqu6v';\""
|
||||
)
|
||||
run("ssh -o StrictHostKeyChecking=no localadministrator@10.10.0.115 'docker ps --format \"{{.Names}} {{.Status}}\" | grep ira4' 2>&1")
|
||||
|
||||
c.close()
|
||||
c.close()
|
||||
print("SUCCESS" if success else "CHECK_LOGS")
|
||||
66
deploy/revert-ns-godaddy.py
Normal file
66
deploy/revert-ns-godaddy.py
Normal file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Force GoDaddy nameservers back to domaincontrol."""
|
||||
import json
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
from datetime import datetime, timezone
|
||||
|
||||
KEY = "2wYVpc3VT4_Gi9AsNqzJ6BtSfNz6nLDuo"
|
||||
SECRET = "NvC3qexGAU7Rd5QExUTH6z"
|
||||
DOMAIN = "exposedgays.com"
|
||||
GD_NS = ["ns71.domaincontrol.com", "ns72.domaincontrol.com"]
|
||||
GD = "https://api.godaddy.com/v1"
|
||||
|
||||
|
||||
def gd(path, method="GET", body=None):
|
||||
req = urllib.request.Request(
|
||||
GD + path,
|
||||
data=json.dumps(body).encode() if body is not None else None,
|
||||
method=method,
|
||||
headers={
|
||||
"Authorization": f"sso-key {KEY}:{SECRET}",
|
||||
"Content-Type": "application/json",
|
||||
"Accept": "application/json",
|
||||
},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
raw = r.read().decode()
|
||||
return r.status, json.loads(raw) if raw else {}
|
||||
except urllib.error.HTTPError as e:
|
||||
raw = e.read().decode()
|
||||
try:
|
||||
return e.code, json.loads(raw)
|
||||
except Exception:
|
||||
return e.code, {"raw": raw[:800]}
|
||||
|
||||
|
||||
def show(label):
|
||||
code, d = gd(f"/domains/{DOMAIN}")
|
||||
print(f"{label}: HTTP {code} NS={d.get('nameServers') if code == 200 else d}")
|
||||
|
||||
|
||||
show("Before")
|
||||
bodies = [
|
||||
{"nameServers": GD_NS},
|
||||
{
|
||||
"nameServers": GD_NS,
|
||||
"consent": {
|
||||
"agreedAt": datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%S.000Z"),
|
||||
"agreedBy": "96.94.95.105",
|
||||
"agreementKeys": ["DNRA"],
|
||||
},
|
||||
},
|
||||
]
|
||||
for i, body in enumerate(bodies, 1):
|
||||
print(f"\nPATCH attempt {i}: {body}")
|
||||
code, res = gd(f"/domains/{DOMAIN}", "PATCH", body)
|
||||
print(f"HTTP {code}: {res}")
|
||||
show(f"After attempt {i}")
|
||||
if code in (200, 204):
|
||||
ns = gd(f"/domains/{DOMAIN}")[1].get("nameServers", [])
|
||||
if ns == GD_NS:
|
||||
print("SUCCESS: NS reverted to GoDaddy")
|
||||
break
|
||||
time.sleep(3)
|
||||
51
deploy/scan-tunnel-tokens.py
Normal file
51
deploy/scan-tunnel-tokens.py
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Find a CF token that can edit tunnel ingress."""
|
||||
import json
|
||||
import re
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
tokens = set()
|
||||
for host, user in [("10.10.0.1", "admin"), ("10.10.0.10", "localadministrator")]:
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect(host, username=user, password=PASS, timeout=15)
|
||||
cmd = (
|
||||
f"echo '{PASS}' | sudo -S bash -c \"grep -rho 'cfut_[A-Za-z0-9_-]{{20,}}' "
|
||||
"/etc/infra /etc/cloudflared /root /home/localadministrator 2>/dev/null | sort -u\""
|
||||
if host == "10.10.0.10"
|
||||
else "grep -o 'cfut_[A-Za-z0-9_-]\\{20,\\}' /usr/local/etc/wan-dns-failover.env"
|
||||
)
|
||||
_, o, e = c.exec_command(cmd, timeout=120)
|
||||
o.channel.recv_exit_status()
|
||||
for line in (o.read() + e.read()).decode().splitlines():
|
||||
if line.startswith("cfut_"):
|
||||
tokens.add(line.strip())
|
||||
c.close()
|
||||
|
||||
print(f"Scanning {len(tokens)} tokens for tunnel config access...")
|
||||
|
||||
def probe(tok):
|
||||
req = urllib.request.Request(
|
||||
f"{CF}/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations",
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
data = json.load(r)
|
||||
ingress = data.get("result", {}).get("config", {}).get("ingress", [])
|
||||
eg = [i.get("hostname") for i in ingress if i.get("hostname") and "exposedgays" in i.get("hostname", "")]
|
||||
return "OK", len(ingress), eg
|
||||
except urllib.error.HTTPError as e:
|
||||
err = json.loads(e.read().decode())
|
||||
return e.code, err.get("errors", [{}])[0].get("message", "")[:60], []
|
||||
|
||||
for tok in sorted(tokens):
|
||||
status, count, eg = probe(tok)
|
||||
print(f"{tok[:20]}... -> {status} {count} exposedgays={eg}")
|
||||
159
deploy/setup-cloudflare-dns.py
Normal file
159
deploy/setup-cloudflare-dns.py
Normal file
@@ -0,0 +1,159 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add exposedgays.com to Cloudflare + tunnel CNAME records."""
|
||||
import json
|
||||
import re
|
||||
import time
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
DOMAIN = "exposedgays.com"
|
||||
TUNNEL_CNAME = "03079a26-f14c-4622-b463-ba54a24f7472.cfargotunnel.com"
|
||||
CF_API = "https://api.cloudflare.com/client/v4"
|
||||
ACCOUNT_ID = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
|
||||
TOKENS = []
|
||||
|
||||
|
||||
def load_tokens():
|
||||
# pfSense production token
|
||||
try:
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
text = (o.read() + e.read()).decode()
|
||||
c.close()
|
||||
m = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", text)
|
||||
if m:
|
||||
TOKENS.append(("pfsense", m.group(1)))
|
||||
except Exception as ex:
|
||||
print("pfsense token:", ex)
|
||||
|
||||
# onsethost env fallback
|
||||
TOKENS.append(("apply_mail", "cfut_zlUt5lAKVsu7qIcRCHJnyhAvpJVF5jx24HlrIEn9a8fcd6a1"))
|
||||
TOKENS.append(("onsethost", "cfut_IOm1DJW4pDkPCuQA6UoOjqO6Tcv2zU10y2PSw82m379f7db3"))
|
||||
|
||||
|
||||
def cf(token, path, method="GET", data=None):
|
||||
req = urllib.request.Request(
|
||||
f"{CF_API}{path}",
|
||||
data=json.dumps(data).encode() if data is not None else None,
|
||||
method=method,
|
||||
headers={"Authorization": f"Bearer {token}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=45) as r:
|
||||
return json.load(r)
|
||||
except urllib.error.HTTPError as e:
|
||||
return json.loads(e.read().decode())
|
||||
|
||||
|
||||
def find_zone(token):
|
||||
for q in [f"name={DOMAIN}", f"name={DOMAIN}&status=active", f"name={DOMAIN}&status=pending"]:
|
||||
res = cf(token, f"/zones?{q}")
|
||||
if res.get("result"):
|
||||
return res["result"][0]
|
||||
# paginate all zones
|
||||
page = 1
|
||||
while page <= 10:
|
||||
res = cf(token, f"/zones?per_page=50&page={page}")
|
||||
for z in res.get("result", []):
|
||||
if z["name"] == DOMAIN:
|
||||
return z
|
||||
if len(res.get("result", [])) < 50:
|
||||
break
|
||||
page += 1
|
||||
return None
|
||||
|
||||
|
||||
def create_zone(token):
|
||||
return cf(
|
||||
token,
|
||||
"/zones",
|
||||
"POST",
|
||||
{"name": DOMAIN, "account": {"id": ACCOUNT_ID}, "type": "full", "jump_start": False},
|
||||
)
|
||||
|
||||
|
||||
def list_records(token, zid):
|
||||
page, out = 1, []
|
||||
while True:
|
||||
res = cf(token, f"/zones/{zid}/dns_records?per_page=100&page={page}")
|
||||
batch = res.get("result", [])
|
||||
out.extend(batch)
|
||||
if len(batch) < 100:
|
||||
break
|
||||
page += 1
|
||||
return out
|
||||
|
||||
|
||||
def upsert_tunnel_cname(token, zid, existing, name):
|
||||
body = {"type": "CNAME", "name": name, "content": TUNNEL_CNAME, "ttl": 1, "proxied": True}
|
||||
if existing:
|
||||
if existing["type"] == "CNAME" and TUNNEL_CNAME in existing.get("content", "") and existing.get("proxied"):
|
||||
print(f"OK {name} already on tunnel")
|
||||
return True
|
||||
res = cf(token, f"/zones/{zid}/dns_records/{existing['id']}", "PATCH", body)
|
||||
action = "PATCH"
|
||||
else:
|
||||
res = cf(token, f"/zones/{zid}/dns_records", "POST", body)
|
||||
action = "POST"
|
||||
print(f"{action} {name}: success={res.get('success')} errors={res.get('errors')}")
|
||||
return res.get("success")
|
||||
|
||||
|
||||
def main():
|
||||
load_tokens()
|
||||
token = None
|
||||
zone = None
|
||||
|
||||
for label, tok in TOKENS:
|
||||
z = find_zone(tok)
|
||||
print(f"[{label}] zone lookup:", "found" if z else "not found")
|
||||
if z:
|
||||
token, zone = tok, z
|
||||
print(f" using {label} token, zone {z['id']} status={z['status']}")
|
||||
break
|
||||
|
||||
if not zone:
|
||||
for label, tok in TOKENS:
|
||||
res = create_zone(tok)
|
||||
print(f"[{label}] create zone:", res.get("success"), res.get("errors"))
|
||||
if res.get("success"):
|
||||
token, zone = tok, res["result"]
|
||||
break
|
||||
|
||||
if not zone:
|
||||
raise RuntimeError("Could not find or create exposedgays.com zone with available tokens")
|
||||
|
||||
zid = zone["id"]
|
||||
nameservers = zone.get("name_servers", [])
|
||||
status = zone.get("status")
|
||||
|
||||
records = list_records(token, zid)
|
||||
apex = next((r for r in records if r["name"] == DOMAIN and r["type"] in ("A", "CNAME", "AAAA")), None)
|
||||
www = next((r for r in records if r["name"] == f"www.{DOMAIN}" and r["type"] in ("A", "CNAME", "AAAA")), None)
|
||||
print(f"Before apex: {apex and (apex['type'], apex.get('content'))}")
|
||||
print(f"Before www: {www and (www['type'], www.get('content'))}")
|
||||
|
||||
upsert_tunnel_cname(token, zid, apex, DOMAIN)
|
||||
upsert_tunnel_cname(token, zid, www, f"www.{DOMAIN}")
|
||||
|
||||
print("\n=== Cloudflare nameservers ===")
|
||||
for ns in nameservers:
|
||||
print(f" {ns}")
|
||||
print(f"Zone status: {status}")
|
||||
if status != "active":
|
||||
print("\n>>> Update GoDaddy NS to the Cloudflare nameservers above <<<")
|
||||
|
||||
time.sleep(5)
|
||||
for r in list_records(token, zid):
|
||||
if "exposedgays" in r["name"]:
|
||||
print(f"Final: {r['type']} {r['name']} -> {r['content']} proxied={r.get('proxied')}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
13
deploy/test-ingress-rule.py
Normal file
13
deploy/test-ingress-rule.py
Normal file
@@ -0,0 +1,13 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for url in ["https://exposedgays.com/", "https://www.exposedgays.com/", "https://thepinkpulse.com/"]:
|
||||
cmd = f"echo '{PASS}' | sudo -S cloudflared tunnel ingress rule '{url}' --config /etc/cloudflared/config.yml 2>&1"
|
||||
print(f"\n=== {url} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=20)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read()+e.read()).decode())
|
||||
c.close()
|
||||
17
deploy/test-onsethost-subdomain.py
Normal file
17
deploy/test-onsethost-subdomain.py
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"curl -sSI --max-time 10 -k -H 'Host: exposedgays.onsethost.com' https://127.0.0.1/ | head -8",
|
||||
"curl -sSI --max-time 10 https://exposedgays.onsethost.com/ 2>&1 | head -8",
|
||||
"dig @1.1.1.1 exposedgays.onsethost.com A +short",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=20)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode())
|
||||
c.close()
|
||||
40
deploy/test-onsethost-token.py
Normal file
40
deploy/test-onsethost-token.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import urllib.error
|
||||
import urllib.request
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
_, o, e = c.exec_command(
|
||||
"grep CLOUDFLARE_API_TOKEN /home/localadministrator/coder/onsethost/.env.local",
|
||||
timeout=20,
|
||||
)
|
||||
o.channel.recv_exit_status()
|
||||
line = (o.read() + e.read()).decode().strip()
|
||||
tok = line.split("=", 1)[1].strip()
|
||||
c.close()
|
||||
|
||||
print("token prefix:", tok[:12], "len:", len(tok))
|
||||
|
||||
for path in [
|
||||
"/user/tokens/verify",
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations",
|
||||
"/zones?name=exposedgays.com",
|
||||
]:
|
||||
req = urllib.request.Request(
|
||||
f"https://api.cloudflare.com/client/v4{path}",
|
||||
headers={"Authorization": f"Bearer {tok}", "Content-Type": "application/json"},
|
||||
)
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
data = json.load(r)
|
||||
print(path, "OK", data.get("success"), "errors=", data.get("errors"))
|
||||
except urllib.error.HTTPError as e:
|
||||
err = json.loads(e.read().decode())
|
||||
print(path, e.code, err.get("errors"))
|
||||
28
deploy/try-cf-api-accounts.py
Normal file
28
deploy/try-cf-api-accounts.py
Normal file
@@ -0,0 +1,28 @@
|
||||
#!/usr/bin/env python3
|
||||
import json, re, urllib.request, urllib.error, paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient(); c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read()+e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
def call(path, method="GET", body=None):
|
||||
req = urllib.request.Request(CF+path, data=json.dumps(body).encode() if body else None, method=method,
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r: return r.status, json.load(r)
|
||||
except urllib.error.HTTPError as e: return e.code, json.loads(e.read().decode())
|
||||
|
||||
for path in ["/accounts", f"/accounts/{ACCOUNT}/cfd_tunnel", f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/configurations"]:
|
||||
code, res = call(path)
|
||||
print(path, code, "success=", res.get("success"), "errors=", res.get("errors"))
|
||||
if res.get("result") and path.endswith("configurations"):
|
||||
ing = res["result"].get("config", {}).get("ingress", [])
|
||||
print(" ingress count", len(ing))
|
||||
print(" exposedgays", [i.get("hostname") for i in ing if i.get("hostname") and "exposed" in i.get("hostname","")])
|
||||
40
deploy/try-cf-graphql.py
Normal file
40
deploy/try-cf-graphql.py
Normal file
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env python3
|
||||
import json, re, urllib.request, paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
|
||||
c = paramiko.SSHClient(); c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read()+e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
query = """
|
||||
query {
|
||||
viewer {
|
||||
accounts(filter: {accountTag: "%s"}) {
|
||||
tunnels(filter: {uuid: "%s"}) {
|
||||
id
|
||||
name
|
||||
config {
|
||||
ingress {
|
||||
hostname
|
||||
service
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
""" % (ACCOUNT, TUNNEL)
|
||||
|
||||
req = urllib.request.Request(
|
||||
"https://api.cloudflare.com/client/v4/graphql",
|
||||
data=json.dumps({"query": query}).encode(),
|
||||
method="POST",
|
||||
headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"},
|
||||
)
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
print(json.dumps(json.load(r), indent=2)[:5000])
|
||||
20
deploy/try-cloudflared-cli.py
Normal file
20
deploy/try-cloudflared-cli.py
Normal file
@@ -0,0 +1,20 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CREDS = f"/etc/cloudflared/{TUNNEL}.json"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
cmds = [
|
||||
"cloudflared tunnel ingress --help 2>&1 | head -30",
|
||||
f"echo '{PASS}' | sudo -S cloudflared tunnel --cred-file {CREDS} ingress validate /etc/cloudflared/config.yml 2>&1",
|
||||
f"echo '{PASS}' | sudo -S cloudflared tunnel --cred-file {CREDS} ingress rule list 2>&1 | head -20",
|
||||
"cloudflared tunnel --help 2>&1 | grep -i 'local\\|remote\\|config' | head -15",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd[:85]} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=60)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read()+e.read()).decode(errors="replace")[:3000])
|
||||
c.close()
|
||||
24
deploy/try-route-dns.py
Normal file
24
deploy/try-route-dns.py
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CREDS = f"/etc/cloudflared/{TUNNEL}.json"
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
|
||||
cmds = [
|
||||
f"echo '{PASS}' | sudo -S cloudflared tunnel --cred-file {CREDS} route dns --help 2>&1 | head -25",
|
||||
f"echo '{PASS}' | sudo -S cloudflared tunnel --cred-file {CREDS} route dns {TUNNEL} www.exposedgays.com 2>&1",
|
||||
f"echo '{PASS}' | sudo -S cloudflared tunnel --cred-file {CREDS} route dns {TUNNEL} exposedgays.com 2>&1",
|
||||
"dig @1.1.1.1 www.exposedgays.com CNAME +short",
|
||||
"curl -sSI --max-time 15 https://www.exposedgays.com/ | head -10",
|
||||
]
|
||||
for cmd in cmds:
|
||||
print(f"\n=== {cmd[:90]} ===")
|
||||
_, o, e = c.exec_command(cmd, timeout=60)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode(errors="replace")[:3000])
|
||||
c.close()
|
||||
30
deploy/try-tunnel-token-api.py
Normal file
30
deploy/try-tunnel-token-api.py
Normal file
@@ -0,0 +1,30 @@
|
||||
#!/usr/bin/env python3
|
||||
import json, re, urllib.request, urllib.error, paramiko
|
||||
PASS = "Bbt9115xty9176!"
|
||||
ACCOUNT = "2599c23bbb1255dbb73e8d34b4115fda"
|
||||
TUNNEL = "03079a26-f14c-4622-b463-ba54a24f7472"
|
||||
CF = "https://api.cloudflare.com/client/v4"
|
||||
|
||||
c = paramiko.SSHClient(); c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.1", username="admin", password=PASS, timeout=15)
|
||||
_, o, e = c.exec_command("cat /usr/local/etc/wan-dns-failover.env", timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
TOK = re.search(r"CLOUDFLARE_API_TOKEN=([^\s\"']+)", (o.read()+e.read()).decode()).group(1)
|
||||
c.close()
|
||||
|
||||
endpoints = [
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/token",
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/connections",
|
||||
f"/accounts/{ACCOUNT}/cfd_tunnel/{TUNNEL}/routes?per_page=50",
|
||||
f"/accounts/{ACCOUNT}/teamnet/routes?per_page=50",
|
||||
f"/accounts/{ACCOUNT}/teamnet/virtual_networks",
|
||||
]
|
||||
for path in endpoints:
|
||||
req = urllib.request.Request(CF+path, headers={"Authorization": f"Bearer {TOK}", "Content-Type": "application/json"})
|
||||
try:
|
||||
with urllib.request.urlopen(req, timeout=30) as r:
|
||||
data = json.load(r)
|
||||
print(path, "OK", json.dumps(data)[:400])
|
||||
except urllib.error.HTTPError as e:
|
||||
body = e.read().decode()
|
||||
print(path, e.code, body[:200])
|
||||
18
deploy/verify-ns.py
Normal file
18
deploy/verify-ns.py
Normal file
@@ -0,0 +1,18 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
PASS = "Bbt9115xty9176!"
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password=PASS, timeout=30)
|
||||
for cmd in [
|
||||
"dig @1.1.1.1 exposedgays.com NS +short",
|
||||
"dig @8.8.8.8 exposedgays.com NS +short",
|
||||
"dig @ns71.domaincontrol.com exposedgays.com NS +short 2>&1 | head -5",
|
||||
]:
|
||||
print(f"$ {cmd}")
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
o.channel.recv_exit_status()
|
||||
print((o.read() + e.read()).decode())
|
||||
print()
|
||||
c.close()
|
||||
21
deploy/verify.py
Normal file
21
deploy/verify.py
Normal file
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env python3
|
||||
import paramiko
|
||||
|
||||
c = paramiko.SSHClient()
|
||||
c.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
c.connect("10.10.0.10", username="localadministrator", password="Bbt9115xty9176!", timeout=20)
|
||||
|
||||
cmds = [
|
||||
"curl -sS -o /dev/null -w '%{http_code}' -k -H 'Host: exposedgays.com' https://127.0.0.1/",
|
||||
"curl -sS -o /dev/null -w '%{http_code}' http://10.10.0.115:3000/ 2>/dev/null || echo no_direct",
|
||||
"ssh -o StrictHostKeyChecking=no localadministrator@10.10.0.115 'docker inspect ira4bw4nhbvm87o7s3fgqu6v-214624968606 --format \"{{json .Config.Labels}}\"' 2>&1 | head -c 2000",
|
||||
"docker exec coolify-db psql -U coolify -d coolify -t -A -c \"SELECT fqdn,status FROM applications WHERE uuid='ira4bw4nhbvm87o7s3fgqu6v';\"",
|
||||
]
|
||||
|
||||
for cmd in cmds:
|
||||
print("$", cmd[:120])
|
||||
_, o, e = c.exec_command(cmd, timeout=30)
|
||||
print(o.read().decode()[:2500])
|
||||
print()
|
||||
|
||||
c.close()
|
||||
Reference in New Issue
Block a user