v2: ID verification by state, 15 photos/5 albums, 20 quick replies, logo, mobile polish
This commit is contained in:
12
README.md
12
README.md
@@ -75,6 +75,18 @@ exposedgays/
|
||||
└── package.json
|
||||
```
|
||||
|
||||
## Auth (Supabase)
|
||||
|
||||
Auth is wired via `src/lib/auth/context.tsx` + `AuthModal.tsx`:
|
||||
|
||||
- **Join Free** in nav → email/password signup or login
|
||||
- **Continue Anonymously** → `signInAnonymously()` (enable in Supabase Dashboard → Auth → Providers → Anonymous)
|
||||
- Profile page syncs to `profiles` table when signed in
|
||||
|
||||
Enable on https://supabase.onsethost.com → Authentication → Providers:
|
||||
- Email (confirm email optional for dev)
|
||||
- Anonymous sign-ins
|
||||
|
||||
## Supabase Setup
|
||||
|
||||
### 1. Create project
|
||||
|
||||
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()
|
||||
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()
|
||||
24
public/logo.svg
Normal file
24
public/logo.svg
Normal file
@@ -0,0 +1,24 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 120 120" fill="none">
|
||||
<defs>
|
||||
<linearGradient id="eg-grad" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#ff2d6b"/>
|
||||
<stop offset="50%" stop-color="#8b2fc9"/>
|
||||
<stop offset="100%" stop-color="#ff2d6b"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="eg-heart" x1="0%" y1="0%" x2="100%" y2="100%">
|
||||
<stop offset="0%" stop-color="#ff6b9d"/>
|
||||
<stop offset="100%" stop-color="#ff2d6b"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
<circle cx="60" cy="60" r="56" fill="#1a1220" stroke="url(#eg-grad)" stroke-width="3"/>
|
||||
<circle cx="60" cy="60" r="44" fill="url(#eg-grad)" opacity="0.15"/>
|
||||
<!-- Cute heart with sparkle -->
|
||||
<path d="M60 88 C60 88 28 68 28 48 C28 36 36 30 44 30 C50 30 56 34 60 40 C64 34 70 30 76 30 C84 30 92 36 92 48 C92 68 60 88 60 88Z" fill="url(#eg-heart)"/>
|
||||
<path d="M48 42 C48 38 52 36 54 38" stroke="#fff" stroke-width="2.5" stroke-linecap="round" opacity="0.6"/>
|
||||
<!-- EG monogram -->
|
||||
<text x="60" y="72" text-anchor="middle" font-family="system-ui, sans-serif" font-weight="800" font-size="18" fill="#fff" opacity="0.9">EG</text>
|
||||
<!-- Sparkle stars -->
|
||||
<circle cx="88" cy="28" r="3" fill="#fff" opacity="0.9"/>
|
||||
<circle cx="32" cy="32" r="2" fill="#fff" opacity="0.6"/>
|
||||
<path d="M88 22 L89 26 L93 27 L89 28 L88 32 L87 28 L83 27 L87 26 Z" fill="#ffd700"/>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -6,17 +6,19 @@
|
||||
"display": "standalone",
|
||||
"background_color": "#0d0a0f",
|
||||
"theme_color": "#ff2d6b",
|
||||
"orientation": "portrait-primary",
|
||||
"orientation": "any",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
"src": "/logo.svg",
|
||||
"sizes": "any",
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/icon-512.png",
|
||||
"src": "/logo.svg",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
"type": "image/svg+xml",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -9,7 +9,7 @@ function ChatContent() {
|
||||
const userId = searchParams.get("user");
|
||||
|
||||
return (
|
||||
<div className="h-[calc(100vh-3.5rem)] md:h-[calc(100vh-3.5rem)]">
|
||||
<div className="h-[calc(100dvh-7rem)] md:h-[calc(100dvh-3.5rem)]">
|
||||
<ChatPanel dmUserId={userId} />
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -92,5 +92,41 @@
|
||||
|
||||
/* PWA safe areas */
|
||||
.safe-bottom {
|
||||
padding-bottom: env(safe-area-inset-bottom, 0);
|
||||
padding-bottom: max(env(safe-area-inset-bottom, 0px), 4px);
|
||||
}
|
||||
.safe-top {
|
||||
padding-top: env(safe-area-inset-top, 0);
|
||||
}
|
||||
|
||||
/* ID verification blur — nude photos in restricted states */
|
||||
.id-blur img,
|
||||
.id-blur .explicit-content {
|
||||
filter: blur(28px) !important;
|
||||
}
|
||||
.id-blur:hover img,
|
||||
.id-blur:hover .explicit-content {
|
||||
filter: blur(20px) !important;
|
||||
}
|
||||
|
||||
/* Mobile touch targets & scroll */
|
||||
.touch-manipulation {
|
||||
touch-action: manipulation;
|
||||
}
|
||||
@supports (height: 100dvh) {
|
||||
.min-h-screen {
|
||||
min-height: 100dvh;
|
||||
}
|
||||
}
|
||||
|
||||
/* Prevent iOS zoom on input focus */
|
||||
@media (max-width: 767px) {
|
||||
input, select, textarea {
|
||||
font-size: 16px !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Overscroll containment for chat */
|
||||
.overscroll-contain {
|
||||
overscroll-behavior: contain;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
@@ -10,6 +10,10 @@ export const metadata: Metadata = {
|
||||
description:
|
||||
"100% free gay cruising map, chat, spots directory, and sex shop. No subscriptions ever.",
|
||||
manifest: "/manifest.json",
|
||||
icons: {
|
||||
icon: "/logo.svg",
|
||||
apple: "/logo.svg",
|
||||
},
|
||||
appleWebApp: {
|
||||
capable: true,
|
||||
statusBarStyle: "black-translucent",
|
||||
@@ -21,7 +25,7 @@ export const viewport: Viewport = {
|
||||
themeColor: "#ff2d6b",
|
||||
width: "device-width",
|
||||
initialScale: 1,
|
||||
maximumScale: 1,
|
||||
viewportFit: "cover",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user