diff --git a/app.py b/app.py index 2fe4968..cdcaa9c 100644 --- a/app.py +++ b/app.py @@ -385,6 +385,8 @@ Sells residential SOCKS5 and HTTP CONNECT proxies with global geo-targeting ## API - GET /api/v1/info -> service info (JSON) - GET /api/v1/plans -> residential GB plans (JSON) +- POST /api/v1/proxy -> one-call purchase (JSON: plan_id, country, city, username, password) -> creds + BTCPay checkout URL +- GET /api/v1/order/ -> payment status (pending/paid) - GET /api/v1/dedicated/locations?product=9 -> ISP dedicated locations (JSON) - GET /api/v1/dedicated/locations?product=3 -> datacenter dedicated locations (JSON) - Human signup: {SITE_HOST}/signup @@ -433,6 +435,74 @@ def api_plans(): return jsonify({"plans": ps}) +@app.route("/api/v1/proxy", methods=["POST"]) +def api_proxy(): + """Agent-facing one-call purchase: create account + invoice, return creds + checkout URL.""" + data = request.get_json(silent=True) or {} + username = (data.get("username") or "").strip() + password = data.get("password") or "" + plan_id = data.get("plan_id") or data.get("plan") + country = (data.get("country") or "").strip().upper() + city = (data.get("city") or "").strip() + + d = db.get_db() + plan = d.execute("SELECT * FROM plans WHERE id=?", (plan_id,)).fetchone() if plan_id else None + if not plan: + plan = d.execute("SELECT * FROM plans WHERE active=1 ORDER BY gb LIMIT 1").fetchone() + if not plan: + d.close() + return jsonify({"error": "no plans"}), 500 + + user_row = d.execute("SELECT * FROM users WHERE username=?", (username,)).fetchone() if username else None + if user_row is None: + username = username or ("u" + db.random_token(10)) + password = password or db.random_token(16) + proxy_pass = db.random_token(18) + d.execute("INSERT INTO users (username,password_hash,proxy_password_hash,proxy_password_plain," + "active,country,city,sticky,balance_gb,created_at) VALUES (?,?,?,?,0,?,?,1,0,?)", + (username, db.scrypt_hash(password), db.scrypt_hash(proxy_pass), proxy_pass, + country or "US", city, int(time.time()))) + d.commit() + uid = d.execute("SELECT id FROM users WHERE username=?", (username,)).fetchone()["id"] + else: + uid = user_row["id"] + proxy_pass = user_row["proxy_password_plain"] + if country: + d.execute("UPDATE users SET country=?, city=? WHERE id=?", (country, city or "", uid)) + d.commit() + + order_id = db.random_token(12) + d.execute("INSERT INTO orders (user_id,plan_id,invoice_id,amount_sats,status,created_at) " + "VALUES (?,?,?,?,'pending',?)", + (uid, plan["id"], order_id, plan["price_sats"], int(time.time()))) + d.commit() + d.close() + inv = create_invoice(plan["price_sats"], order_id) + + return jsonify({ + "username": username, + "password": password, + "proxy_password": proxy_pass, + "socks5": f"socks5://{username}:{proxy_pass}@{PROXY['host']}:{PROXY['socks_port']}", + "http": f"http://{username}:{proxy_pass}@{PROXY['host']}:{PROXY['http_port']}", + "country": country or "US", + "city": city, + "plan": {"id": plan["id"], "name": plan["name"], "gb": plan["gb"], "price_sats": plan["price_sats"]}, + "invoice": {"id": order_id, "checkout_url": inv["checkoutLink"], "amount_sats": plan["price_sats"]}, + "status_url": f"https://{SITE_HOST}/api/v1/order/{order_id}", + }) + + +@app.route("/api/v1/order/") +def api_order_status(order_id): + d = db.get_db() + o = d.execute("SELECT * FROM orders WHERE invoice_id=?", (order_id,)).fetchone() + d.close() + if not o: + return jsonify({"error": "not found"}), 404 + return jsonify({"order_id": order_id, "status": o["status"], "amount_sats": o["amount_sats"]}) + + @app.route("/sitemap.xml") def sitemap(): urls = [f"https://{SITE_HOST}/{p}" for p in ["", "plans", "signup", "login", "llms.txt"]] diff --git a/router-forward.sh b/router-forward.sh new file mode 100644 index 0000000..2d03ae1 --- /dev/null +++ b/router-forward.sh @@ -0,0 +1,27 @@ +#!/bin/sh +# Add Pleiades proxy port-forwards (CT777 .178): SOCKS5 :1080 + HTTP :8080. +# Appends to the JFFS firewall-start hook (reboot-persistent) + applies now. + +cp /jffs/scripts/firewall-start /jffs/scripts/firewall-start.bak-pleiades-$(date +%s) + +cat >> /jffs/scripts/firewall-start << 'EOF' +# Pleiades proxy store (CT777 .178): SOCKS5 :1080 + HTTP :8080 inbound +iptables -t nat -D VSERVER -p tcp --dport 1080 -j DNAT --to-destination 10.30.20.178:1080 2>/dev/null +iptables -t nat -A VSERVER -p tcp --dport 1080 -j DNAT --to-destination 10.30.20.178:1080 +iptables -I FORWARD 1 -p tcp -d 10.30.20.178 --dport 1080 -j ACCEPT +iptables -t nat -D VSERVER -p tcp --dport 8080 -j DNAT --to-destination 10.30.20.178:8080 2>/dev/null +iptables -t nat -A VSERVER -p tcp --dport 8080 -j DNAT --to-destination 10.30.20.178:8080 +iptables -I FORWARD 1 -p tcp -d 10.30.20.178 --dport 8080 -j ACCEPT +EOF + +# apply immediately +for P in "1080:1080" "8080:8080"; do + EXT=${P%%:*}; DPT=${P##*:} + iptables -t nat -A VSERVER -p tcp --dport $EXT -j DNAT --to-destination 10.30.20.178:$DPT + iptables -I FORWARD 1 -p tcp -d 10.30.20.178 --dport $DPT -j ACCEPT +done + +echo "=== VSERVER (pleiades) ===" +iptables -t nat -S VSERVER | grep -E "dport 1080|dport 8080" +echo "=== FORWARD (pleiades) ===" +iptables -S FORWARD | grep "10.30.20.178"