@fudrouter/fsrouter 0.6.102 → 0.6.104
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/dist/lib/appUpdater.js +2 -2
- package/dist/lib/dataDir.js +35 -10
- package/dist/lib/db/migrations/002-composite-unique.js +60 -6
- package/dist/lib/mitmAliasCache.js +1 -1
- package/dist/lib/tunnel/tailscale/tailscale.js +1 -1
- package/dist/lib/updater/updater.js +1 -1
- package/dist/routes/automation/codebuddy/[id]/route.js +1 -1
- package/dist/routes/automation/codebuddy/route.js +1 -1
- package/dist/routes/automation/openvecta/route.js +1 -1
- package/package.json +1 -1
package/dist/lib/appUpdater.js
CHANGED
|
@@ -13,7 +13,7 @@ function killMitmByPidFile() {
|
|
|
13
13
|
const mitmPidFile = path.join(
|
|
14
14
|
process.platform === "win32"
|
|
15
15
|
? path.join(process.env.APPDATA || "", "9router")
|
|
16
|
-
: path.join(os.homedir(), ".
|
|
16
|
+
: path.join(os.homedir(), ".fsrouter"),
|
|
17
17
|
"mitm",
|
|
18
18
|
".mitm.pid"
|
|
19
19
|
);
|
|
@@ -108,7 +108,7 @@ function getDataDir() {
|
|
|
108
108
|
if (process.platform === "win32") {
|
|
109
109
|
return path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "9router");
|
|
110
110
|
}
|
|
111
|
-
return path.join(os.homedir(), ".
|
|
111
|
+
return path.join(os.homedir(), ".fsrouter");
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
function resolveBundledUpdaterPath() {
|
package/dist/lib/dataDir.js
CHANGED
|
@@ -2,7 +2,8 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "path";
|
|
3
3
|
import os from "os";
|
|
4
4
|
|
|
5
|
-
const APP_NAME = "
|
|
5
|
+
const APP_NAME = "fsrouter";
|
|
6
|
+
const LEGACY_APP_NAME = "amrouter";
|
|
6
7
|
|
|
7
8
|
function defaultDir() {
|
|
8
9
|
if (process.platform === "win32") {
|
|
@@ -11,19 +12,43 @@ function defaultDir() {
|
|
|
11
12
|
return path.join(os.homedir(), `.${APP_NAME}`);
|
|
12
13
|
}
|
|
13
14
|
|
|
15
|
+
function legacyDir() {
|
|
16
|
+
if (process.platform === "win32") {
|
|
17
|
+
return path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), LEGACY_APP_NAME);
|
|
18
|
+
}
|
|
19
|
+
return path.join(os.homedir(), `.${LEGACY_APP_NAME}`);
|
|
20
|
+
}
|
|
21
|
+
|
|
14
22
|
export function getDataDir() {
|
|
15
23
|
const configured = process.env.DATA_DIR;
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
+
if (configured) {
|
|
25
|
+
try {
|
|
26
|
+
fs.mkdirSync(configured, { recursive: true });
|
|
27
|
+
return configured;
|
|
28
|
+
} catch (e) {
|
|
29
|
+
if (e?.code === "EACCES" || e?.code === "EPERM") {
|
|
30
|
+
console.warn(`[DATA_DIR] '${configured}' not writable → fallback ~/.${APP_NAME}`);
|
|
31
|
+
return defaultDir();
|
|
32
|
+
}
|
|
33
|
+
throw e;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const dir = defaultDir();
|
|
37
|
+
// Backward-compat: if the new dir doesn't exist yet but the legacy `amrouter`
|
|
38
|
+
// dir does, migrate it (rename) so existing data isn't lost on first launch.
|
|
39
|
+
if (!fs.existsSync(dir)) {
|
|
40
|
+
const old = legacyDir();
|
|
41
|
+
if (fs.existsSync(old)) {
|
|
42
|
+
try {
|
|
43
|
+
fs.renameSync(old, dir);
|
|
44
|
+
console.log(`[DATA_DIR] Migrated legacy data folder ${old} → ${dir}`);
|
|
45
|
+
} catch (e) {
|
|
46
|
+
console.warn(`[DATA_DIR] Could not migrate ${old} → ${dir}: ${e.message}`);
|
|
47
|
+
}
|
|
24
48
|
}
|
|
25
|
-
throw e;
|
|
26
49
|
}
|
|
50
|
+
try { fs.mkdirSync(dir, { recursive: true }); } catch { /* ignore */ }
|
|
51
|
+
return dir;
|
|
27
52
|
}
|
|
28
53
|
|
|
29
54
|
export const DATA_DIR = getDataDir();
|
|
@@ -1,20 +1,73 @@
|
|
|
1
1
|
// Recreate codebuddyAccounts table to drop the single-column UNIQUE(email)
|
|
2
2
|
// and replace it with composite UNIQUE(email, provider).
|
|
3
|
+
// Idempotent: handles both legacy `ammailAlias` and renamed `fsmailAlias`
|
|
4
|
+
// columns in the old table (older DBs may have run migration 003/004 before
|
|
5
|
+
// this migration's version was recorded, leaving `fsmailAlias` in _old).
|
|
3
6
|
export default {
|
|
4
7
|
version: 2,
|
|
5
8
|
name: "composite-unique-codebuddy-accounts",
|
|
6
9
|
up(db) {
|
|
10
|
+
// If the table doesn't exist at all, just create it fresh and bail.
|
|
11
|
+
const exists = db.get(
|
|
12
|
+
"SELECT 1 FROM sqlite_master WHERE type='table' AND name='codebuddyAccounts'"
|
|
13
|
+
);
|
|
14
|
+
if (!exists) {
|
|
15
|
+
db.exec(`
|
|
16
|
+
CREATE TABLE codebuddyAccounts (
|
|
17
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
18
|
+
email TEXT NOT NULL,
|
|
19
|
+
password TEXT NOT NULL,
|
|
20
|
+
profileDir TEXT,
|
|
21
|
+
fsmailAlias TEXT,
|
|
22
|
+
signupMethod TEXT DEFAULT 'google',
|
|
23
|
+
apiKey TEXT,
|
|
24
|
+
apiKeyStatus TEXT DEFAULT 'pending',
|
|
25
|
+
lastError TEXT,
|
|
26
|
+
lastRunAt INTEGER,
|
|
27
|
+
createdAt TEXT NOT NULL,
|
|
28
|
+
provider TEXT DEFAULT 'codebuddy',
|
|
29
|
+
canvaEnrolled INTEGER DEFAULT 0,
|
|
30
|
+
UNIQUE(email, provider)
|
|
31
|
+
)
|
|
32
|
+
`);
|
|
33
|
+
db.exec("CREATE INDEX IF NOT EXISTS idx_cba_email ON codebuddyAccounts(email)");
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Detect which alias column the existing (old) table uses.
|
|
38
|
+
const info = db.all("PRAGMA table_info(codebuddyAccounts)");
|
|
39
|
+
const cols = info.map((c) => c.name);
|
|
40
|
+
const hasFsmail = cols.includes("fsmailAlias");
|
|
41
|
+
const hasAmmail = cols.includes("ammailAlias");
|
|
42
|
+
const oldAlias = hasFsmail ? "fsmailAlias" : hasAmmail ? "ammailAlias" : null;
|
|
43
|
+
|
|
44
|
+
// Already on the target schema (composite unique + fsmailAlias) — skip.
|
|
45
|
+
if (hasFsmail) {
|
|
46
|
+
const idx = db.all(
|
|
47
|
+
"SELECT 1 FROM sqlite_master WHERE type='index' AND name='sqlite_autoindex_codebuddyAccounts_1'"
|
|
48
|
+
);
|
|
49
|
+
// The composite UNIQUE was added by this migration; if it's already there, nothing to do.
|
|
50
|
+
try {
|
|
51
|
+
db.get("SELECT email, provider FROM codebuddyAccounts LIMIT 1");
|
|
52
|
+
} catch { /* ignore */ }
|
|
53
|
+
// If a composite index already exists we can safely skip.
|
|
54
|
+
const compIdx = db.all(
|
|
55
|
+
"SELECT * FROM sqlite_master WHERE type='index' AND tbl_name='codebuddyAccounts' AND sql LIKE '%email%provider%'"
|
|
56
|
+
);
|
|
57
|
+
if (compIdx.length > 0) return;
|
|
58
|
+
}
|
|
59
|
+
|
|
7
60
|
// 1. Rename existing table
|
|
8
61
|
db.exec("ALTER TABLE codebuddyAccounts RENAME TO codebuddyAccounts_old");
|
|
9
62
|
|
|
10
|
-
// 2. Create new table with unique(email, provider) constraint
|
|
63
|
+
// 2. Create new table with unique(email, provider) constraint + fsmailAlias
|
|
11
64
|
db.exec(`
|
|
12
65
|
CREATE TABLE codebuddyAccounts (
|
|
13
66
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
14
67
|
email TEXT NOT NULL,
|
|
15
68
|
password TEXT NOT NULL,
|
|
16
69
|
profileDir TEXT,
|
|
17
|
-
|
|
70
|
+
fsmailAlias TEXT,
|
|
18
71
|
signupMethod TEXT DEFAULT 'google',
|
|
19
72
|
apiKey TEXT,
|
|
20
73
|
apiKeyStatus TEXT DEFAULT 'pending',
|
|
@@ -30,14 +83,15 @@ export default {
|
|
|
30
83
|
// 3. Recreate index
|
|
31
84
|
db.exec("CREATE INDEX IF NOT EXISTS idx_cba_email ON codebuddyAccounts(email)");
|
|
32
85
|
|
|
33
|
-
// 4. Copy data from old table,
|
|
86
|
+
// 4. Copy data from old table, mapping whichever alias column exists.
|
|
87
|
+
const aliasSelect = oldAlias || "NULL";
|
|
34
88
|
db.exec(`
|
|
35
89
|
INSERT OR IGNORE INTO codebuddyAccounts (
|
|
36
|
-
id, email, password, profileDir,
|
|
90
|
+
id, email, password, profileDir, fsmailAlias, signupMethod,
|
|
37
91
|
apiKey, apiKeyStatus, lastError, lastRunAt, createdAt, provider, canvaEnrolled
|
|
38
92
|
)
|
|
39
|
-
SELECT
|
|
40
|
-
id, email, password, profileDir,
|
|
93
|
+
SELECT
|
|
94
|
+
id, email, password, profileDir, ${aliasSelect}, signupMethod,
|
|
41
95
|
apiKey, apiKeyStatus, lastError, lastRunAt, createdAt, provider, canvaEnrolled
|
|
42
96
|
FROM codebuddyAccounts_old
|
|
43
97
|
`);
|
|
@@ -8,7 +8,7 @@ import os from "os";
|
|
|
8
8
|
const DATA_DIR = process.env.DATA_DIR
|
|
9
9
|
|| (process.platform === "win32"
|
|
10
10
|
? path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "9router")
|
|
11
|
-
: path.join(os.homedir(), ".
|
|
11
|
+
: path.join(os.homedir(), ".fsrouter"));
|
|
12
12
|
|
|
13
13
|
const CACHE_FILE = path.join(DATA_DIR, "mitm", "aliases.json");
|
|
14
14
|
|
|
@@ -460,7 +460,7 @@ function isDaemonTunMode() {
|
|
|
460
460
|
* Start tailscaled.
|
|
461
461
|
* - With sudoPassword: TUN mode (root) → Funnel TLS works
|
|
462
462
|
* - Without: userspace-networking fallback (no sudo, but Funnel TLS unstable)
|
|
463
|
-
* State always lives in ~/.
|
|
463
|
+
* State always lives in ~/.fsrouter/tailscale/ via --statedir.
|
|
464
464
|
*/
|
|
465
465
|
export async function startDaemonWithPassword(sudoPassword) {
|
|
466
466
|
if (IS_WINDOWS) {
|
|
@@ -28,7 +28,7 @@ function getDataDir() {
|
|
|
28
28
|
if (process.platform === "win32") {
|
|
29
29
|
return path.join(process.env.APPDATA || path.join(os.homedir(), "AppData", "Roaming"), "9router");
|
|
30
30
|
}
|
|
31
|
-
return path.join(os.homedir(), ".
|
|
31
|
+
return path.join(os.homedir(), ".fsrouter");
|
|
32
32
|
}
|
|
33
33
|
const updateDir = path.join(getDataDir(), "update");
|
|
34
34
|
try { fs.mkdirSync(updateDir, { recursive: true }); } catch { /* best effort */ }
|
|
@@ -255,7 +255,7 @@ function executeCodeBuddySignupSingle(accountId, jobId, settings) {
|
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
257
|
try {
|
|
258
|
-
const logDir = path.join(process.env.DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE || "/tmp", ".
|
|
258
|
+
const logDir = path.join(process.env.DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE || "/tmp", ".fsrouter"), "logs");
|
|
259
259
|
fs.mkdirSync(logDir, { recursive: true });
|
|
260
260
|
fs.appendFileSync(`${logDir}/automation_spawn.log`, `[${new Date().toISOString()}] [id]/route.js spawning python: ${venvPython} ${args.join(" ")}\n`);
|
|
261
261
|
}
|
|
@@ -898,7 +898,7 @@ function executeCodeBuddySignup(accountId, jobId, idx, settings, jobStartTimes =
|
|
|
898
898
|
}
|
|
899
899
|
}
|
|
900
900
|
try {
|
|
901
|
-
const logDir = path.join(process.env.DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE || "/tmp", ".
|
|
901
|
+
const logDir = path.join(process.env.DATA_DIR || path.join(process.env.HOME || process.env.USERPROFILE || "/tmp", ".fsrouter"), "logs");
|
|
902
902
|
fs.mkdirSync(logDir, { recursive: true });
|
|
903
903
|
fs.appendFileSync(`${logDir}/automation_spawn.log`, `[${new Date().toISOString()}] route.js spawning python: ${venvPython} ${args.join(" ")}\n`);
|
|
904
904
|
}
|
|
@@ -241,7 +241,7 @@ export async function POST_handler(req, res) {
|
|
|
241
241
|
// ── Background runner ─────────────────────────────────────────────────────────
|
|
242
242
|
async function runOpenVectaSignup(jobId, email, venvPython, args) {
|
|
243
243
|
try {
|
|
244
|
-
const logDir = path.join(process.env.DATA_DIR || path.join(process.env.HOME || "/tmp", ".
|
|
244
|
+
const logDir = path.join(process.env.DATA_DIR || path.join(process.env.HOME || "/tmp", ".fsrouter"), "logs");
|
|
245
245
|
fs.mkdirSync(logDir, { recursive: true });
|
|
246
246
|
fs.appendFileSync(`${logDir}/automation_spawn.log`, `[${new Date().toISOString()}] openvecta spawning python: ${venvPython} ${args.join(" ")}\n`);
|
|
247
247
|
}
|