Add admin dashboard, Umami beacon, README
This commit is contained in:
65
README.md
Normal file
65
README.md
Normal 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
52
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():
|
||||
|
||||
68
templates/admin.html
Normal file
68
templates/admin.html
Normal 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 %}
|
||||
11
templates/admin_login.html
Normal file
11
templates/admin_login.html
Normal 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 %}
|
||||
@@ -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:type" content="website">
|
||||
<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>
|
||||
<body>
|
||||
<nav>
|
||||
|
||||
Reference in New Issue
Block a user