@fudrouter/fsrouter 0.6.92 → 0.6.93

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fudrouter/fsrouter",
3
- "version": "0.6.92",
3
+ "version": "0.6.93",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "9Router v2 — Express Backend (API, SSE, DB, Auth, MITM)",
@@ -9,8 +9,16 @@
9
9
  "fsrouter": "bin/cli.cjs"
10
10
  },
11
11
  "scripts": {
12
- "start": "node dist/server.js"
12
+ "start": "node dist/server.js",
13
+ "postinstall": "node scripts/postinstall.cjs"
13
14
  },
15
+ "files": [
16
+ "dist",
17
+ "public",
18
+ "open-sse",
19
+ "bin",
20
+ "scripts"
21
+ ],
14
22
  "dependencies": {
15
23
  "bcryptjs": "^3.0.3",
16
24
  "better-sqlite3": "^12.11.1",
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Post-install: wire automation symlinks so pm2 (cwd=/root) can find the
4
+ * Python venv, source tree and browser profiles used by the signup scripts.
5
+ *
6
+ * pm2 runs the server with cwd=/root, and the automation routes resolve paths
7
+ * relative to process.cwd() (e.g. .venv/bin/python, src/automation/*.py,
8
+ * profiles/<provider>). On this VPS the real files live under /root/AMRouter,
9
+ * so we symlink /root/{.venv,src,profiles} -> /root/AMRouter/{...}.
10
+ *
11
+ * Idempotent and non-fatal: never throws (npm install must succeed even if
12
+ * the symlinks can't be created).
13
+ */
14
+ const fs = require("fs");
15
+ const path = require("path");
16
+
17
+ function log(msg) {
18
+ // eslint-disable-next-line no-console
19
+ console.log(`[fsrouter:postinstall] ${msg}`);
20
+ }
21
+
22
+ function symlinkIfMissing(linkPath, targetPath) {
23
+ try {
24
+ if (!fs.existsSync(targetPath)) {
25
+ // target missing — only create the symlink if the link itself is absent
26
+ if (fs.existsSync(linkPath) || isSymlink(linkPath)) {
27
+ // leave existing link alone; just warn if it points nowhere
28
+ if (!fs.existsSync(linkPath)) fs.unlinkSync(linkPath);
29
+ }
30
+ log(`skip ${linkPath} (target ${targetPath} not found)`);
31
+ return;
32
+ }
33
+ if (isSymlink(linkPath)) {
34
+ const cur = fs.readlinkSync(linkPath);
35
+ if (cur === targetPath) {
36
+ log(`ok ${linkPath} -> ${targetPath}`);
37
+ return;
38
+ }
39
+ fs.unlinkSync(linkPath);
40
+ } else if (fs.existsSync(linkPath)) {
41
+ log(`skip ${linkPath} (exists and is not a symlink)`);
42
+ return;
43
+ }
44
+ fs.symlinkSync(targetPath, linkPath, "dir");
45
+ log(`linked ${linkPath} -> ${targetPath}`);
46
+ } catch (e) {
47
+ log(`warn: could not link ${linkPath} -> ${targetPath}: ${e.message}`);
48
+ }
49
+ }
50
+
51
+ function isSymlink(p) {
52
+ try {
53
+ return fs.lstatSync(p).isSymbolicLink();
54
+ } catch {
55
+ return false;
56
+ }
57
+ }
58
+
59
+ function main() {
60
+ // Prefer the canonical AMRouter checkout; fall back to the installed package.
61
+ const candidates = [
62
+ "/root/AMRouter",
63
+ path.resolve(__dirname, ".."), // package root when installed
64
+ ];
65
+ let base = null;
66
+ for (const c of candidates) {
67
+ if (fs.existsSync(c)) {
68
+ base = c;
69
+ break;
70
+ }
71
+ }
72
+ if (!base) {
73
+ log("no AMRouter base found; skipping symlinks");
74
+ return;
75
+ }
76
+ log(`base=${base}`);
77
+
78
+ // Virtualenv (venv python)
79
+ symlinkIfMissing("/root/.venv", path.join(base, ".venv"));
80
+ // Source tree (src/automation/*.py)
81
+ symlinkIfMissing("/root/src", path.join(base, "backend", "src"));
82
+ // Browser profiles used by automation signup scripts
83
+ symlinkIfMissing("/root/profiles", path.join(base, "backend", "profiles"));
84
+ }
85
+
86
+ try {
87
+ main();
88
+ } catch (e) {
89
+ log(`unexpected error: ${e.message}`);
90
+ }
@@ -1,158 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- cf_token_via_session.py
4
- Login ke akun Cloudflare yang ada, buat Workers AI token via session cookie API.
5
- Usage:
6
- python cf_token_via_session.py --email X --password Y --account-id Z
7
- """
8
- import argparse, json, time, sys
9
- import requests
10
- from pathlib import Path
11
-
12
- def log(m): print(json.dumps({"step": m}), flush=True)
13
- def die(m): print(json.dumps({"status": "error", "error": m}), flush=True); sys.exit(1)
14
-
15
-
16
- def create_token_with_cookies(cookies: dict, account_id: str) -> str | None:
17
- """POST /api/v4/user/tokens using browser session cookies."""
18
- base = "https://dash.cloudflare.com"
19
- sess = requests.Session()
20
- sess.cookies.update(cookies)
21
- sess.headers.update({
22
- "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120",
23
- "Referer": f"{base}/profile/api-tokens/create",
24
- "Origin": base,
25
- "Accept": "application/json",
26
- })
27
-
28
- # A1: Get permission groups to find Workers AI ID
29
- log("Fetching permission groups...")
30
- r = sess.get(f"{base}/api/v4/user/tokens/permission_groups", timeout=15)
31
- log(f"Permission groups status: {r.status_code}")
32
- if r.status_code != 200:
33
- log(f"Permission groups response: {r.text[:300]}")
34
- return None
35
-
36
- pg_data = r.json()
37
- log(f"Total groups: {len(pg_data.get('result', []))}")
38
-
39
- # Find Workers AI groups
40
- workers_ai_ids = []
41
- for pg in pg_data.get('result', []):
42
- name = pg.get('name', '')
43
- if 'Workers AI' in name:
44
- log(f"Found: {name} -> {pg['id']}")
45
- workers_ai_ids.append({'id': pg['id'], 'name': name})
46
-
47
- if not workers_ai_ids:
48
- log("No Workers AI permission groups found")
49
- log(f"All groups: {[p['name'] for p in pg_data.get('result', [])][:20]}")
50
- return None
51
-
52
- # Prefer Workers AI:Read for safety, otherwise take first
53
- target_id = workers_ai_ids[0]['id']
54
- for pg in workers_ai_ids:
55
- if 'Read' in pg['name']:
56
- target_id = pg['id']
57
- break
58
-
59
- log(f"Using permission group ID: {target_id}")
60
-
61
- # A2: Create token
62
- payload = {
63
- "name": "9router-workers-ai",
64
- "policies": [{
65
- "effect": "allow",
66
- "resources": {f"com.cloudflare.api.account.{account_id}": "*"},
67
- "permission_groups": [{"id": target_id}]
68
- }]
69
- }
70
-
71
- log(f"Creating token with payload: {json.dumps(payload)}")
72
- r2 = sess.post(f"{base}/api/v4/user/tokens", json=payload, timeout=15)
73
- log(f"Create token status: {r2.status_code}")
74
- resp2 = r2.json()
75
- log(f"Response: {json.dumps(resp2)[:500]}")
76
-
77
- if resp2.get('success'):
78
- return resp2['result'].get('value', '')
79
- return None
80
-
81
-
82
- def main():
83
- parser = argparse.ArgumentParser()
84
- parser.add_argument("--email", required=True)
85
- parser.add_argument("--password", required=True)
86
- parser.add_argument("--account-id", required=True, dest="account_id")
87
- parser.add_argument("--headless", action="store_true")
88
- args = parser.parse_args()
89
-
90
- try:
91
- from camoufox.sync_api import Camoufox
92
- except ImportError:
93
- die("Camoufox tidak terinstall")
94
-
95
- log(f"Login ke CF sebagai {args.email}...")
96
- with Camoufox(headless=args.headless, os="windows") as browser:
97
- page = browser.new_page()
98
-
99
- # Navigate to login
100
- page.goto("https://dash.cloudflare.com/login", wait_until="domcontentloaded", timeout=30000)
101
- time.sleep(3)
102
-
103
- # Fill login form
104
- try:
105
- email_inp = page.locator("input[name='email'], input[type='email']").first
106
- if email_inp.is_visible(timeout=5000):
107
- email_inp.fill(args.email)
108
- time.sleep(0.3)
109
-
110
- pass_inp = page.locator("input[type='password']").first
111
- if pass_inp.is_visible(timeout=3000):
112
- pass_inp.fill(args.password)
113
- time.sleep(0.3)
114
-
115
- for sel in ["button[type='submit']", "button:has-text('Log in')", "button:has-text('Sign in')"]:
116
- try:
117
- btn = page.locator(sel).first
118
- if btn.is_visible(timeout=2000):
119
- btn.click()
120
- break
121
- except Exception:
122
- continue
123
-
124
- log("Login submitted, waiting...")
125
- time.sleep(6)
126
- except Exception as e:
127
- log(f"Login form error: {e}")
128
-
129
- log(f"Current URL: {page.url}")
130
- page.screenshot(path="/tmp/cf_login_state.png")
131
-
132
- # Check if logged in
133
- if "dash.cloudflare.com" not in page.url:
134
- die(f"Login failed, URL: {page.url}")
135
-
136
- # Extract cookies
137
- cookies_list = page.context.cookies()
138
- cookies = {c['name']: c['value'] for c in cookies_list}
139
- log(f"Got {len(cookies)} cookies: {list(cookies.keys())[:10]}")
140
-
141
- # Create token via API
142
- token = create_token_with_cookies(cookies, args.account_id)
143
-
144
- if token:
145
- log(f"TOKEN BERHASIL: {token[:12]}...")
146
- print(json.dumps({
147
- "status": "success",
148
- "workers_ai_token": token,
149
- "account_id": args.account_id,
150
- "email": args.email
151
- }), flush=True)
152
- else:
153
- page.screenshot(path="/tmp/cf_session_debug.png")
154
- die("Token creation via session failed")
155
-
156
-
157
- if __name__ == "__main__":
158
- main()