"""VDSok provisioner — idempotent order + deploy for the KeyFarm VPS plane. Reads the API key from tmp/vdsok_token.txt (never from argv/env literals). Flow: /me -> /servers (reuse same-name box if exists) -> /account balance -> POST /servers (Idempotency-Key, cheapest tariff by default) -> poll order -> save creds to tmp/vdsok_server_.txt -> optional deploy via kf_install.sh. usage: python tmp/vdsok_provision.py [--name keyfarm-nl] [--tariff 16] [--os ubuntu-24-04] [--months 1] [--shard 1] [--shards 4] [--deploy] """ import argparse import json import os import sys import time import urllib.error import urllib.request import uuid TOK_FILE = os.path.expanduser(r"~\tmp\vdsok_token.txt") BASE = "https://vdsok.guru/api/v1" def call(path, method="GET", body=None, idem=None, tok=None): h = {"Authorization": "***" + tok, "Accept": "application/json"} if idem: h["Idempotency-Key"] = idem data = json.dumps(body).encode() if body is not None else None if data: h["Content-Type"] = "application/json" req = urllib.request.Request(BASE + path, method=method, headers=h, data=data) try: with urllib.request.urlopen(req, timeout=60) as r: return r.status, json.loads(r.read().decode() or "null") except urllib.error.HTTPError as e: raw = e.read().decode(errors="replace") try: raw = json.loads(raw) except Exception: pass return e.code, raw def main(): ap = argparse.ArgumentParser() ap.add_argument("--name", default="keyfarm-hunter") ap.add_argument("--tariff", type=int, default=0, help="0 = cheapest available") ap.add_argument("--os", dest="os_slug", default="ubuntu-24-04") ap.add_argument("--months", type=int, default=1) ap.add_argument("--shard", type=int, default=0) ap.add_argument("--shards", type=int, default=4) ap.add_argument("--deploy", action="store_true") a = ap.parse_args() tok = open(TOK_FILE, encoding="utf-8").read().strip() st, me = call("/me", tok=tok) if st != 200: sys.exit(f"/me failed: {st} {me}") print(f"key ok: account_id={me.get('account_id')} sandbox={me.get('is_test')}") st, servers = call("/servers?limit=100", tok=tok) for s in (servers.get("data") or []): if s.get("name") == a.name: print(f"REUSE existing server id={s['id']} ip={s.get('primary_ip')} " f"status={s.get('status')}") save(a.name, s, None) return st, acct = call("/account", tok=tok) bal = (acct.get("balance") or {}).get("amount") if isinstance(acct.get("balance"), dict) else acct.get("balance") print(f"balance: {bal} {acct.get('currency')}") st, tars = call("/catalog/tariffs?limit=100", tok=tok) avail = [t for t in tars.get("data", []) if t.get("available")] if not avail: sys.exit("no available tariffs") t = next((x for x in avail if x["id"] == a.tariff), None) if a.tariff else None t = t or min(avail, key=lambda x: float(x["price_monthly"])) price = float(t["price_monthly"]) * a.months print(f"tariff: id={t['id']} {t['name']} {t['price_monthly']}/mo x{a.months} " f"= {price:.2f} {t['currency']} | {t['vcpu']}vCPU " f"{int(t['ram_mb'])//1024}GB {int(t['disk_mb'])//1024}GB {t['location']}") if bal is not None and float(bal) < price: sys.exit(f"INSUFFICIENT BALANCE {bal} < {price:.2f} — top up first") st, res = call("/servers", "POST", {"tariff_id": t["id"], "os": a.os_slug, "name": a.name, "months": a.months}, idem=str(uuid.uuid4()), tok=tok) if st not in (200, 201, 202): sys.exit(f"order failed: {st} {res}") srv = res.get("server", {}) pw = res.get("root_password") print(f"ordered: id={srv.get('id')} charged={res.get('charged')} " f"balance_after={res.get('balance_after')}") # poll for IP for i in range(24): st, s2 = call(f"/servers/{srv.get('id')}", tok=tok) ip = (s2 or {}).get("primary_ip") if ip: srv = s2 break time.sleep(10) save(a.name, srv, pw) if a.deploy and srv.get("primary_ip") and pw: print("deploy: scp tmp/kf_install.sh + tree, then " f"bash install.sh {a.shard} {a.shards} (see handoff)") def save(name, srv, pw): out = os.path.expanduser(rf"~\tmp\vdsok_server_{name}.txt") rec = {"server_id": srv.get("id"), "name": name, "primary_ip": srv.get("primary_ip"), "status": srv.get("status"), "tariff": (srv.get("tariff") or {}).get("name"), "root_password": pw or "(shown once at order; see earlier record)"} with open(out, "w", encoding="utf-8") as f: json.dump(rec, f, indent=1) os.chmod(out, 0o600) print(f"saved: {out} ip={rec['primary_ip']}") if __name__ == "__main__": main()