Add admin dashboard, Umami beacon, README

This commit is contained in:
drjones
2026-09-19 19:17:44 -07:00
parent be0a4d8563
commit bac4a695f6
5 changed files with 197 additions and 0 deletions

65
README.md Normal file
View File

@@ -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/<id>` — 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`.

52
app.py
View File

@@ -363,6 +363,58 @@ def poll_provisioning():
return ok, failed 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 ---------- # ---------- agent discovery ----------
@app.route("/llms.txt") @app.route("/llms.txt")
def llms_txt(): def llms_txt():

68
templates/admin.html Normal file
View File

@@ -0,0 +1,68 @@
{% extends "base.html" %}
{% block body %}
<h2>Admin</h2>
<div class="stats-row">
<div class="card stat"><span class="lbl">Revenue (paid)</span><span class="val">{{ stats.revenue_sats }} sats</span></div>
<div class="card stat"><span class="lbl">IPRoyal balance</span><span class="val">${{ ipr_balance }}</span></div>
<div class="card stat"><span class="lbl">Users</span><span class="val">{{ stats.users }} <span style="font-size:14px;color:var(--muted)">({{ stats.active }} active)</span></span></div>
</div>
<div class="stats-row">
<div class="card stat"><span class="lbl">GB sold</span><span class="val">{{ stats.gb_sold }} GB</span></div>
<div class="card stat"><span class="lbl">Pending orders</span><span class="val">{{ stats.pending_orders }}</span></div>
<div class="card stat"><span class="lbl">Dedicated IPs</span><span class="val">{{ dedicated|length }}</span></div>
</div>
<div class="card" style="margin-top:18px">
<h3>Credit a user</h3>
<form method="post" action="/admin/credit" style="display:flex;gap:10px;align-items:flex-end">
<div style="flex:1"><label>Username</label><input type="text" name="username" required></div>
<div style="width:120px"><label>GB</label><input type="number" name="gb" step="0.1" value="5" required></div>
<input type="submit" value="Credit" style="width:auto;margin:0;padding:11px 20px">
</form>
</div>
<div class="dash-grid" style="margin-top:18px">
<div class="card">
<h3>Recent orders</h3>
<table style="width:100%;font-size:13px;border-collapse:collapse">
<tr style="color:var(--muted);text-align:left"><th>ID</th><th>User</th><th>Plan</th><th>sats</th><th>Status</th></tr>
{% for o in orders %}
<tr style="border-top:1px solid var(--border)">
<td style="padding:4px">{{ o.id }}</td><td>{{ o.user_id }}</td><td>{{ o.plan_id }}</td>
<td>{{ o.amount_sats }}</td><td style="color:{{ 'var(--ok)' if o.status=='paid' else 'var(--warn)' }}">{{ o.status }}</td>
</tr>
{% endfor %}
</table>
</div>
<div class="card">
<h3>Dedicated IPs</h3>
<table style="width:100%;font-size:13px;border-collapse:collapse">
<tr style="color:var(--muted);text-align:left"><th>Type</th><th>Location</th><th>IP</th><th>Status</th></tr>
{% for d in dedicated %}
<tr style="border-top:1px solid var(--border)">
<td style="padding:4px">{{ d.product_name|replace(' Dedicated','') }}</td>
<td>{{ d.location_name|truncate(20) }}</td>
<td class="mono">{{ d.proxy_host or '—' }}</td>
<td style="color:{{ 'var(--ok)' if d.status=='active' else 'var(--warn)' }}">{{ d.status }}</td>
</tr>
{% endfor %}
</table>
</div>
</div>
<div class="card" style="margin-top:18px">
<h3>Users</h3>
<table style="width:100%;font-size:13px;border-collapse:collapse">
<tr style="color:var(--muted);text-align:left"><th>ID</th><th>Username</th><th>Active</th><th>Balance</th><th>Geo</th></tr>
{% for u in users %}
<tr style="border-top:1px solid var(--border)">
<td style="padding:4px">{{ u.id }}</td><td>{{ u.username }}</td>
<td>{{ '✓' if u.active else '—' }}</td>
<td>{{ '%.2f'|format(u.balance_gb) }} GB</td>
<td>{{ u.country or '—' }}{% if u.city %}/{{ u.city }}{% endif %}</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends "base.html" %}
{% block body %}
<div class="form-card card">
<h2>Admin</h2>
<form method="post">
<label>Admin password</label>
<input type="password" name="password" required autofocus>
<input type="submit" value="Unlock">
</form>
</div>
{% endblock %}

View File

@@ -10,6 +10,7 @@
<meta property="og:description" content="Clean, fast, geo-targeted residential proxies. No KYC. Pay with Bitcoin. Built for agents and humans alike."> <meta property="og:description" content="Clean, fast, geo-targeted residential proxies. No KYC. Pay with Bitcoin. Built for agents and humans alike.">
<meta property="og:type" content="website"> <meta property="og:type" content="website">
<link rel="stylesheet" href="/static/style.css"> <link rel="stylesheet" href="/static/style.css">
<script async src="https://analytics.thetempleofdoom.com/script.js" data-website-id="0e876bc2-1407-4e05-a8d3-3219ee9e5198"></script>
</head> </head>
<body> <body>
<nav> <nav>