From bac4a695f6d568a08d667eed6cd71a94f7067cc2 Mon Sep 17 00:00:00 2001 From: drjones Date: Sat, 19 Sep 2026 19:17:44 -0700 Subject: [PATCH] Add admin dashboard, Umami beacon, README --- README.md | 65 ++++++++++++++++++++++++++++++++++++ app.py | 52 +++++++++++++++++++++++++++++ templates/admin.html | 68 ++++++++++++++++++++++++++++++++++++++ templates/admin_login.html | 11 ++++++ templates/base.html | 1 + 5 files changed, 197 insertions(+) create mode 100644 README.md create mode 100644 templates/admin.html create mode 100644 templates/admin_login.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..f2c7550 --- /dev/null +++ b/README.md @@ -0,0 +1,65 @@ +# Clean Proxys — "Pleiades" + +Residential + dedicated proxy store, no-KYC, Bitcoin checkout. SOCKS5 & HTTP. +`clean-proxys.thetempleofdoom.com` (Cloudflare fleet tunnel). Node CT 777 @ 10.30.20.178. + +## What it is +A white-label proxy middleman. Customers (humans *and* AI agents) buy clean +residential/ISP/datacenter proxies with Bitcoin. The gateway authenticates each +user, geo-targets their egress through the IPRoyal upstream, and meters GB usage. + +Three tiers: +1. **Residential rotating** — per-GB, any country/city on the fly. +2. **ISP Dedicated** — one clean residential-ISP IP (premium, $4/30d wholesale). +3. **Datacenter Dedicated** — one static DC IP (budget, $1.80/30d wholesale). + +## Architecture +``` +customer / agent + │ HTTPS + ▼ +CF tunnel → nginx → Flask storefront (gunicorn 127.0.0.1:5000, SQLite) + │ ├─ BTCPay webhook → activate on payment + │ ├─ /admin (revenue, users, orders, inventory) + │ └─ /api/v1/* (agent discovery + one-call purchase) + ▼ +gateway.py (SOCKS5 :1080 + HTTP CONNECT :8080) ← per-user auth → geo → IPRoyal + ▼ +IPRoyal upstream (geo.iproyal.com:12321 residential / dedicated IPs) +``` + +## Components +- `gateway.py` — asyncio SOCKS5 + HTTP CONNECT relay. Per-user scrypt auth, + geo/sticky rewrite, GB byte metering. No traffic-content logging. +- `app.py` — Flask storefront + admin + agent API + BTCPay webhook. +- `db.py` — SQLite schema + auth helpers (scrypt). +- `iproyal.py` — IPRoyal reseller API wrapper (balance/products/orders). +- `admin.py` — CLI (create-user, set-country, add-balance, list). +- `provision.py` — dedicated-IP provisioning poll (systemd `provision.timer`). + +## Services (systemd) +`gateway.service`, `app.service`, `provision.timer` (5-min poll). + +## Agent API +- `GET /llms.txt` — machine-readable service description +- `GET /api/v1/info`, `GET /api/v1/plans` +- `POST /api/v1/proxy` — one-call purchase → creds + BTCPay checkout URL +- `GET /api/v1/order/` — payment status +- `GET /api/v1/dedicated/locations?product=3|9` — dedicated location catalog + +## Key facts (IPRoyal) +- Residential: `geo.iproyal.com:12321`, geo suffix **on the password** + (`_country-US_city-NewYork_session-XXXXXXXX_lifetime-30m`). +- Dedicated: connect **directly to the IP** — `socks5://user:pass@IP:12324`, + `http://user:pass@IP:12323`. Order via `apid.iproyal.com/v1/reseller` + (`X-Access-Token`), poll `GET /orders/{id}` until `confirmed`, read + `proxy_data.proxies[0]` = {username, password, ip}. + +## Deploy +```bash +cd ~/pleiades && tar czf /tmp/e.tar.gz . && scp /tmp/e.tar.gz root@10.30.20.85:/tmp/ \ + && ssh root@10.30.20.85 "pct push 777 /tmp/e.tar.gz /tmp/e.tar.gz && pct exec 777 -- \ + bash -c 'cd /opt/pleiades && tar xzf /tmp/e.tar.gz && systemctl restart gateway app'" +``` + +`config.json` holds secrets (gitignored); see `config.example.json`. diff --git a/app.py b/app.py index cdcaa9c..fd4c7f2 100644 --- a/app.py +++ b/app.py @@ -363,6 +363,58 @@ def poll_provisioning(): return ok, failed +# ---------- admin ---------- +ADMIN_PASSWORD = CFG.get("admin_password", "czapiewski") + + +@app.route("/admin", methods=["GET", "POST"]) +def admin(): + if request.method == "POST": + if request.form.get("password") == ADMIN_PASSWORD: + session["admin"] = True + else: + flash("Bad admin password.", "warn") + if not session.get("admin"): + return render_template("admin_login.html") + d = db.get_db() + stats = { + "users": d.execute("SELECT COUNT(*) c FROM users").fetchone()["c"], + "active": d.execute("SELECT COUNT(*) c FROM users WHERE active=1").fetchone()["c"], + "revenue_sats": d.execute("SELECT COALESCE(SUM(amount_sats),0) s FROM orders WHERE status='paid'").fetchone()["s"], + "pending_orders": d.execute("SELECT COUNT(*) c FROM orders WHERE status='pending'").fetchone()["c"], + "gb_sold": d.execute("SELECT COALESCE(SUM(gb),0) s FROM plans WHERE id IN " + "(SELECT plan_id FROM orders WHERE status='paid')").fetchone()["s"], + } + users = [dict(r) for r in d.execute("SELECT id,username,active,balance_gb,country,city,created_at " + "FROM users ORDER BY id DESC LIMIT 50")] + orders = [dict(r) for r in d.execute("SELECT * FROM orders ORDER BY id DESC LIMIT 20")] + deds = [dict(r) for r in d.execute("SELECT * FROM dedicated_ips ORDER BY id DESC LIMIT 30")] + d.close() + try: + ipr_balance = iproyal.get_balance() + except Exception: + ipr_balance = "unavailable" + return render_template("admin.html", stats=stats, users=users, orders=orders, + dedicated=deds, ipr_balance=ipr_balance) + + +@app.route("/admin/credit", methods=["POST"]) +def admin_credit(): + if not session.get("admin"): + abort(403) + username = request.form.get("username", "").strip() + try: + gb = float(request.form.get("gb", 0)) + except ValueError: + gb = 0 + d = db.get_db() + d.execute("UPDATE users SET balance_gb=balance_gb+?, active=1 WHERE username=?", (gb, username)) + d.commit() + d.close() + flash(f"Credited {gb} GB to {username}", "ok") + return redirect(url_for("admin")) + + # ---------- agent discovery ---------- @app.route("/llms.txt") def llms_txt(): diff --git a/templates/admin.html b/templates/admin.html new file mode 100644 index 0000000..fa9e21f --- /dev/null +++ b/templates/admin.html @@ -0,0 +1,68 @@ +{% extends "base.html" %} +{% block body %} +

Admin

+
+
Revenue (paid){{ stats.revenue_sats }} sats
+
IPRoyal balance${{ ipr_balance }}
+
Users{{ stats.users }} ({{ stats.active }} active)
+
+
+
GB sold{{ stats.gb_sold }} GB
+
Pending orders{{ stats.pending_orders }}
+
Dedicated IPs{{ dedicated|length }}
+
+ +
+

Credit a user

+
+
+
+ +
+
+ +
+
+

Recent orders

+ + + {% for o in orders %} + + + + + {% endfor %} +
IDUserPlansatsStatus
{{ o.id }}{{ o.user_id }}{{ o.plan_id }}{{ o.amount_sats }}{{ o.status }}
+
+ +
+

Dedicated IPs

+ + + {% for d in dedicated %} + + + + + + + {% endfor %} +
TypeLocationIPStatus
{{ d.product_name|replace(' Dedicated','') }}{{ d.location_name|truncate(20) }}{{ d.proxy_host or '—' }}{{ d.status }}
+
+
+ +
+

Users

+ + + {% for u in users %} + + + + + + + {% endfor %} +
IDUsernameActiveBalanceGeo
{{ u.id }}{{ u.username }}{{ '✓' if u.active else '—' }}{{ '%.2f'|format(u.balance_gb) }} GB{{ u.country or '—' }}{% if u.city %}/{{ u.city }}{% endif %}
+
+{% endblock %} diff --git a/templates/admin_login.html b/templates/admin_login.html new file mode 100644 index 0000000..ba91005 --- /dev/null +++ b/templates/admin_login.html @@ -0,0 +1,11 @@ +{% extends "base.html" %} +{% block body %} +
+

Admin

+
+ + + +
+
+{% endblock %} diff --git a/templates/base.html b/templates/base.html index cb28430..0f83c88 100644 --- a/templates/base.html +++ b/templates/base.html @@ -10,6 +10,7 @@ +