@walkhi/code-relax 0.1.0-beta.68 → 0.1.0-beta.69

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.
Files changed (120) hide show
  1. package/dist/bin/manage-relay-admin.mjs +20 -20
  2. package/dist/bin/migrate-relay-db.mjs +109 -109
  3. package/dist/bin/publish-harmony-release.mjs +46 -46
  4. package/dist/bin/self-relay-admin.mjs +8 -8
  5. package/dist/bin/self-relay-demo.mjs +33 -33
  6. package/dist/bin/self-relay-server.mjs +66 -66
  7. package/dist/bin/set-relay-admin-password.mjs +11 -11
  8. package/dist/build-platform.json +1 -1
  9. package/dist/get-lan-networks.ps1 +40 -40
  10. package/dist/shared/app-server-events.cjs +511 -511
  11. package/dist/shared/p2p-data-channel.cjs +72 -72
  12. package/dist/shared/p2p-diagnostics.cjs +28 -28
  13. package/dist/shared/p2p-ice-policy.cjs +22 -22
  14. package/dist/shared/plan-pricing.cjs +32 -32
  15. package/dist/shared/question-attention.cjs +69 -69
  16. package/dist/shared/release-version.cjs +31 -31
  17. package/dist/src/archived-threads.mjs +22 -22
  18. package/dist/src/background-process.mjs +18 -18
  19. package/dist/src/code-browser.mjs +100 -100
  20. package/dist/src/desktop-launcher.mjs +62 -62
  21. package/dist/src/dots-task-files.mjs +52 -52
  22. package/dist/src/execution-health.mjs +26 -26
  23. package/dist/src/lan-socket-server.mjs +1 -1
  24. package/dist/src/message-images.mjs +9 -9
  25. package/dist/src/plan-pricing.mjs +47 -47
  26. package/dist/src/platform/codex-runtime.mjs +3 -3
  27. package/dist/src/platform/credential-store.mjs +19 -19
  28. package/dist/src/platform/desktop-host.mjs +3 -3
  29. package/dist/src/platform/desktop-permission-mode.mjs +17 -17
  30. package/dist/src/platform/desktop-project-registration.mjs +3 -3
  31. package/dist/src/platform/file-opener.mjs +9 -9
  32. package/dist/src/platform/linux/README.md +3 -3
  33. package/dist/src/platform/linux/codex-runtime.mjs +56 -56
  34. package/dist/src/platform/linux/desktop-installation.mjs +36 -36
  35. package/dist/src/platform/linux/desktop-project-registration.mjs +11 -11
  36. package/dist/src/platform/linux/network-info.mjs +11 -11
  37. package/dist/src/platform/linux/project-locations.mjs +17 -17
  38. package/dist/src/platform/linux/resident-startup.mjs +52 -52
  39. package/dist/src/platform/linux/retained-runtime.mjs +76 -76
  40. package/dist/src/platform/linux/service-host.mjs +18 -18
  41. package/dist/src/platform/macos/README.md +3 -3
  42. package/dist/src/platform/macos/desktop-project-registration.mjs +11 -11
  43. package/dist/src/platform/macos/network-info.mjs +8 -8
  44. package/dist/src/platform/macos/project-locations.mjs +8 -8
  45. package/dist/src/platform/macos/resident-startup.mjs +84 -84
  46. package/dist/src/platform/macos/retained-runtime.mjs +127 -127
  47. package/dist/src/platform/macos/service-host.mjs +18 -18
  48. package/dist/src/platform/macos/system.mjs +29 -29
  49. package/dist/src/platform/network-info.mjs +3 -3
  50. package/dist/src/platform/path-identity.mjs +5 -5
  51. package/dist/src/platform/posix/network-candidates.mjs +23 -23
  52. package/dist/src/platform/posix/runtime-paths.mjs +25 -25
  53. package/dist/src/platform/power-inhibitor.mjs +22 -22
  54. package/dist/src/platform/project-locations.mjs +3 -3
  55. package/dist/src/platform/runtime-paths.mjs +8 -8
  56. package/dist/src/platform/select.mjs +5 -5
  57. package/dist/src/platform/service-host.mjs +3 -3
  58. package/dist/src/platform/user-environment.mjs +3 -3
  59. package/dist/src/platform/windows/IsolatedProcess.cs +79 -79
  60. package/dist/src/platform/windows/background-process.mjs +30 -30
  61. package/dist/src/platform/windows/desktop-helper.mjs +179 -179
  62. package/dist/src/platform/windows/desktop-runtime.ps1 +10 -10
  63. package/dist/src/platform/windows/desktop-shortcuts.ps1 +7 -7
  64. package/dist/src/platform/windows/keep-awake.ps1 +20 -20
  65. package/dist/src/platform/windows/network-info.mjs +34 -34
  66. package/dist/src/platform/windows/resident-start.js +20 -20
  67. package/dist/src/platform/windows/resident-start.ps1 +7 -7
  68. package/dist/src/platform/windows/resident-startup.ps1 +83 -83
  69. package/dist/src/platform/windows/resolve-state-root.ps1 +14 -14
  70. package/dist/src/platform/windows/runtime-paths.mjs +20 -20
  71. package/dist/src/platform/windows/rustdesk-video-probe.mjs +167 -167
  72. package/dist/src/platform/windows/start-hidden-console.ps1 +58 -58
  73. package/dist/src/platform/windows/stop-desktop.ps1 +22 -22
  74. package/dist/src/platform/windows/user-environment.ps1 +41 -41
  75. package/dist/src/project-create.mjs +121 -121
  76. package/dist/src/project-management.mjs +1 -1
  77. package/dist/src/project-remove.mjs +55 -55
  78. package/dist/src/project-update.mjs +59 -59
  79. package/dist/src/relay-config-import.mjs +45 -45
  80. package/dist/src/self-relay/account-connections.mjs +52 -52
  81. package/dist/src/self-relay/admin-auth.mjs +59 -59
  82. package/dist/src/self-relay/admin-cloud.mjs +101 -101
  83. package/dist/src/self-relay/admin-login-page.mjs +7 -7
  84. package/dist/src/self-relay/admin-server.mjs +105 -105
  85. package/dist/src/self-relay/admin-state.mjs +127 -127
  86. package/dist/src/self-relay/admin-workstations-page.mjs +95 -95
  87. package/dist/src/self-relay/admin-workstations.mjs +143 -143
  88. package/dist/src/self-relay/android-releases.mjs +61 -61
  89. package/dist/src/self-relay/client.mjs +61 -61
  90. package/dist/src/self-relay/connection-reporting.mjs +38 -38
  91. package/dist/src/self-relay/connector.mjs +13 -2
  92. package/dist/src/self-relay/debug-allowlist.mjs +33 -33
  93. package/dist/src/self-relay/demo.mjs +6 -6
  94. package/dist/src/self-relay/device-info.mjs +27 -27
  95. package/dist/src/self-relay/device-store.mjs +92 -92
  96. package/dist/src/self-relay/lan-authorizations.mjs +71 -71
  97. package/dist/src/self-relay/lifecycle.mjs +278 -278
  98. package/dist/src/self-relay/long-pairing.mjs +40 -40
  99. package/dist/src/self-relay/mysql-state.mjs +118 -118
  100. package/dist/src/self-relay/phone-connections.mjs +55 -55
  101. package/dist/src/self-relay/server.mjs +14 -3
  102. package/dist/src/self-relay/usage-daily.mjs +55 -55
  103. package/dist/src/self-relay/windows-route-helper.mjs +75 -75
  104. package/dist/src/self-relay/wire.mjs +36 -36
  105. package/dist/src/server.mjs +40 -18
  106. package/dist/src/service-control.mjs +82 -82
  107. package/dist/src/service-health.mjs +33 -33
  108. package/dist/src/service-power.mjs +58 -58
  109. package/dist/src/service-restart.mjs +81 -81
  110. package/dist/src/service-update-worker.mjs +42 -42
  111. package/dist/src/service-update.mjs +80 -80
  112. package/dist/src/shared-recovery.mjs +152 -152
  113. package/dist/src/skill-install.mjs +51 -51
  114. package/dist/src/thread-title-generation.mjs +150 -150
  115. package/dist/src/turn-model-history.mjs +47 -47
  116. package/dist/tools/rtc-route-helper.ps1 +99 -99
  117. package/dist/tools/start-rtc-route-helper.ps1 +15 -15
  118. package/dist/web/vendor/markdown-it.LICENSE.txt +22 -22
  119. package/dist/web/vendor/markdown-it.umd.min.js +13 -13
  120. package/package.json +4 -4
@@ -1,20 +1,20 @@
1
- #!/usr/bin/env node
2
- import fs from 'node:fs';
3
- import mysql from 'mysql2/promise';
4
-
5
- const path = process.env.RELAY_MYSQL_CONFIG_FILE;
6
- const action = process.argv[2];
7
- const email = (process.argv[3] || '').trim().toLowerCase();
8
- if (!path || !['--grant', '--revoke', '--list'].includes(action) ||
9
- (action !== '--list' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))) {
10
- throw new Error('Use --grant EMAIL, --revoke EMAIL or --list with RELAY_MYSQL_CONFIG_FILE');
11
- }
12
- const db = await mysql.createConnection({ ...JSON.parse(fs.readFileSync(path, 'utf8')),
13
- dateStrings: true, timezone: 'Z', multipleStatements: false });
14
- try {
15
- if (action === '--grant') await db.execute(`INSERT INTO relay_admin_emails (email,granted_at)
16
- VALUES (?,UTC_TIMESTAMP(3)) ON DUPLICATE KEY UPDATE email=VALUES(email)`, [email]);
17
- else if (action === '--revoke') await db.execute('DELETE FROM relay_admin_emails WHERE email=?', [email]);
18
- const [rows] = await db.query('SELECT email FROM relay_admin_emails ORDER BY email');
19
- console.log(JSON.stringify({ admins: rows.map(row => row.email) }));
20
- } finally { await db.end(); }
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import mysql from 'mysql2/promise';
4
+
5
+ const path = process.env.RELAY_MYSQL_CONFIG_FILE;
6
+ const action = process.argv[2];
7
+ const email = (process.argv[3] || '').trim().toLowerCase();
8
+ if (!path || !['--grant', '--revoke', '--list'].includes(action) ||
9
+ (action !== '--list' && !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email))) {
10
+ throw new Error('Use --grant EMAIL, --revoke EMAIL or --list with RELAY_MYSQL_CONFIG_FILE');
11
+ }
12
+ const db = await mysql.createConnection({ ...JSON.parse(fs.readFileSync(path, 'utf8')),
13
+ dateStrings: true, timezone: 'Z', multipleStatements: false });
14
+ try {
15
+ if (action === '--grant') await db.execute(`INSERT INTO relay_admin_emails (email,granted_at)
16
+ VALUES (?,UTC_TIMESTAMP(3)) ON DUPLICATE KEY UPDATE email=VALUES(email)`, [email]);
17
+ else if (action === '--revoke') await db.execute('DELETE FROM relay_admin_emails WHERE email=?', [email]);
18
+ const [rows] = await db.query('SELECT email FROM relay_admin_emails ORDER BY email');
19
+ console.log(JSON.stringify({ admins: rows.map(row => row.email) }));
20
+ } finally { await db.end(); }
@@ -1,109 +1,109 @@
1
- #!/usr/bin/env node
2
- import fs from 'node:fs';
3
- import { createHash } from 'node:crypto';
4
- import { fileURLToPath } from 'node:url';
5
- import mysql from 'mysql2/promise';
6
- import { relaySchemaVersions } from '../database/versions.mjs';
7
- import { deviceStore } from '../src/self-relay/device-store.mjs';
8
- import { relayAdminState } from '../src/self-relay/admin-state.mjs';
9
-
10
- const command = process.argv[2];
11
- if (!['--schema', '--import', '--check'].includes(command) || !process.env.RELAY_MYSQL_CONFIG_FILE) {
12
- throw new Error('Use --schema, --import or --check with RELAY_MYSQL_CONFIG_FILE');
13
- }
14
- const config = JSON.parse(fs.readFileSync(process.env.RELAY_MYSQL_CONFIG_FILE, 'utf8'));
15
- const db = await mysql.createConnection({ ...config, dateStrings: true, timezone: 'Z', multipleStatements: false });
16
- const sqlTime = value => value.replace('T', ' ').replace(/Z$/, '');
17
- const migrations = relaySchemaVersions;
18
- try {
19
- if (command === '--schema') {
20
- const [existing] = await db.query("SHOW TABLES LIKE 'schema_migrations'");
21
- const [applied] = existing.length ? await db.query('SELECT version, sha256 FROM schema_migrations') : [[]];
22
- const known = new Map(applied.map(row => [row.version, row.sha256]));
23
- if (known.size !== applied.length || [...known.keys()].some(version => !migrations.includes(version))) throw new Error('Unexpected schema version');
24
- for (const version of migrations) {
25
- const source = fs.readFileSync(fileURLToPath(new URL(`../database/${version}.sql`, import.meta.url)), 'utf8');
26
- const sha256 = createHash('sha256').update(source).digest('hex');
27
- if (known.has(version)) {
28
- if (known.get(version) !== sha256) throw new Error(`Migration changed: ${version}`);
29
- continue;
30
- }
31
- if (version !== migrations[0] && !known.has(migrations[migrations.indexOf(version) - 1])) throw new Error('Previous migration missing');
32
- const statements = source.split('\n').filter(line => !line.trimStart().startsWith('--')).join('\n');
33
- for (const statement of statements.split(';').map(item => item.trim()).filter(Boolean)) await db.query(statement);
34
- await db.execute('INSERT INTO schema_migrations (version, sha256) VALUES (?,?)', [version, sha256]);
35
- known.set(version, sha256);
36
- console.log(JSON.stringify({ schema: version, sha256 }));
37
- }
38
- } else {
39
- const [versions] = await db.query('SELECT version FROM schema_migrations');
40
- if (!versions.some(row => row.version === '001_relay_core') || versions.some(row => !migrations.includes(row.version))) throw new Error('Unexpected schema version');
41
- if (command === '--check') {
42
- const names = ['computers', 'mobile_authorizations', 'official_computers', 'announcements', 'relay_settings', 'users', 'user_login_identities'];
43
- if (versions.some(row => row.version === '002_email_accounts')) names.push('email_login_challenges', 'user_sessions', 'email_send_limits');
44
- if (versions.some(row => row.version === '003_relay_usage')) names.push('relay_admin_emails', 'relay_usage_settings', 'relay_usage_totals', 'relay_usage_computer_totals', 'relay_usage_entitlements', 'relay_usage_budgets', 'relay_usage_computer_budgets', 'relay_usage_sessions');
45
- if (versions.some(row => row.version === '004_relay_usage_actions')) names.push('relay_usage_actions');
46
- if (versions.some(row => row.version === '006_relay_diagnostics')) names.push('relay_diagnostics');
47
- if (versions.some(row => row.version === '007_relay_usage_daily')) names.push('relay_usage_user_days', 'relay_usage_computer_days');
48
- const counts = {};
49
- for (const name of names) { const [rows] = await db.query(`SELECT COUNT(*) AS count FROM ${name}`); counts[name] = rows[0].count; }
50
- if (process.env.RELAY_STATE_FILE && process.env.RELAY_ADMIN_STATE_FILE) {
51
- const devices = deviceStore(process.env.RELAY_STATE_FILE).entries();
52
- const admin = relayAdminState(process.env.RELAY_ADMIN_STATE_FILE, 8).read();
53
- const expected = { computers: devices.length,
54
- mobile_authorizations: devices.reduce((sum, item) => sum + item.clients.length, 0),
55
- official_computers: devices.filter(item => item.officialComputer).length,
56
- announcements: admin.announcements.length, relay_settings: 1 };
57
- if (Object.keys(expected).some(name => counts[name] !== expected[name])) throw new Error('Imported row counts differ from JSON');
58
- const [saved] = await db.query('SELECT id,host_hash,status,revision FROM computers');
59
- const original = new Map(devices.map(item => [item.id, item]));
60
- for (const row of saved) {
61
- const item = original.get(row.id);
62
- if (!item || item.hostHash !== row.host_hash || item.status !== row.status || item.revision !== Number(row.revision)) {
63
- throw new Error('Imported computer identity differs from JSON');
64
- }
65
- }
66
- const [authorized] = await db.query('SELECT computer_id,client_id,credential_hash FROM mobile_authorizations');
67
- for (const row of authorized) {
68
- const item = original.get(row.computer_id)?.clients.find(client => client.id === row.client_id);
69
- if (!item || item.hash !== row.credential_hash) throw new Error('Imported mobile authorization differs from JSON');
70
- }
71
- const [settings] = await db.query('SELECT max_relay_sessions FROM relay_settings WHERE id=1');
72
- if (settings[0]?.max_relay_sessions !== admin.maxRelaySessions) throw new Error('Imported relay setting differs from JSON');
73
- console.log(JSON.stringify({ verified: true, counts }));
74
- } else console.log(JSON.stringify(counts));
75
- } else {
76
- const stateFile = process.env.RELAY_STATE_FILE, adminFile = process.env.RELAY_ADMIN_STATE_FILE;
77
- if (!stateFile || !adminFile) throw new Error('RELAY_STATE_FILE and RELAY_ADMIN_STATE_FILE required for import');
78
- const devices = deviceStore(stateFile).entries();
79
- const admin = relayAdminState(adminFile, 8).read();
80
- const [existing] = await db.query('SELECT (SELECT COUNT(*) FROM computers) AS computers, (SELECT COUNT(*) FROM relay_settings) AS settings');
81
- if (existing[0].computers || existing[0].settings) throw new Error('Import target is not empty');
82
- await db.beginTransaction();
83
- try {
84
- for (const item of devices) {
85
- await db.execute('INSERT INTO computers (id,host_hash,status,revision,created_at,updated_at) VALUES (?,?,?,?,?,?)',
86
- [item.id, item.hostHash, item.status, item.revision, sqlTime(item.createdAt), sqlTime(item.updatedAt)]);
87
- for (const client of item.clients) await db.execute(
88
- 'INSERT INTO mobile_authorizations (computer_id,client_id,credential_hash,created_at,updated_at) VALUES (?,?,?,?,?)',
89
- [item.id, client.id, client.hash, sqlTime(client.createdAt), sqlTime(client.updatedAt)]);
90
- if (item.officialComputer) {
91
- const official = item.officialComputer;
92
- await db.execute('INSERT INTO official_computers (computer_id,environment_id,name,hostname,updated_at) VALUES (?,?,?,?,?)',
93
- [item.id, official.environmentId, official.name, official.hostname, sqlTime(official.updatedAt)]);
94
- }
95
- }
96
- await db.execute('INSERT INTO relay_settings (id,max_relay_sessions,updated_at) VALUES (1,?,UTC_TIMESTAMP(3))', [admin.maxRelaySessions]);
97
- for (const item of admin.announcements) await db.execute(
98
- 'INSERT INTO announcements (id,title,message,status,created_at,updated_at,published_at) VALUES (?,?,?,?,?,?,?)',
99
- [item.id, item.title, item.message, item.status, sqlTime(item.createdAt), sqlTime(item.updatedAt),
100
- item.publishedAt ? sqlTime(item.publishedAt) : null]);
101
- await db.commit();
102
- } catch (error) { await db.rollback(); throw error; }
103
- console.log(JSON.stringify({ imported: true, computers: devices.length,
104
- mobileAuthorizations: devices.reduce((sum, item) => sum + item.clients.length, 0),
105
- officialComputers: devices.filter(item => item.officialComputer).length,
106
- announcements: admin.announcements.length }));
107
- }
108
- }
109
- } finally { await db.end(); }
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import { createHash } from 'node:crypto';
4
+ import { fileURLToPath } from 'node:url';
5
+ import mysql from 'mysql2/promise';
6
+ import { relaySchemaVersions } from '../database/versions.mjs';
7
+ import { deviceStore } from '../src/self-relay/device-store.mjs';
8
+ import { relayAdminState } from '../src/self-relay/admin-state.mjs';
9
+
10
+ const command = process.argv[2];
11
+ if (!['--schema', '--import', '--check'].includes(command) || !process.env.RELAY_MYSQL_CONFIG_FILE) {
12
+ throw new Error('Use --schema, --import or --check with RELAY_MYSQL_CONFIG_FILE');
13
+ }
14
+ const config = JSON.parse(fs.readFileSync(process.env.RELAY_MYSQL_CONFIG_FILE, 'utf8'));
15
+ const db = await mysql.createConnection({ ...config, dateStrings: true, timezone: 'Z', multipleStatements: false });
16
+ const sqlTime = value => value.replace('T', ' ').replace(/Z$/, '');
17
+ const migrations = relaySchemaVersions;
18
+ try {
19
+ if (command === '--schema') {
20
+ const [existing] = await db.query("SHOW TABLES LIKE 'schema_migrations'");
21
+ const [applied] = existing.length ? await db.query('SELECT version, sha256 FROM schema_migrations') : [[]];
22
+ const known = new Map(applied.map(row => [row.version, row.sha256]));
23
+ if (known.size !== applied.length || [...known.keys()].some(version => !migrations.includes(version))) throw new Error('Unexpected schema version');
24
+ for (const version of migrations) {
25
+ const source = fs.readFileSync(fileURLToPath(new URL(`../database/${version}.sql`, import.meta.url)), 'utf8');
26
+ const sha256 = createHash('sha256').update(source).digest('hex');
27
+ if (known.has(version)) {
28
+ if (known.get(version) !== sha256) throw new Error(`Migration changed: ${version}`);
29
+ continue;
30
+ }
31
+ if (version !== migrations[0] && !known.has(migrations[migrations.indexOf(version) - 1])) throw new Error('Previous migration missing');
32
+ const statements = source.split('\n').filter(line => !line.trimStart().startsWith('--')).join('\n');
33
+ for (const statement of statements.split(';').map(item => item.trim()).filter(Boolean)) await db.query(statement);
34
+ await db.execute('INSERT INTO schema_migrations (version, sha256) VALUES (?,?)', [version, sha256]);
35
+ known.set(version, sha256);
36
+ console.log(JSON.stringify({ schema: version, sha256 }));
37
+ }
38
+ } else {
39
+ const [versions] = await db.query('SELECT version FROM schema_migrations');
40
+ if (!versions.some(row => row.version === '001_relay_core') || versions.some(row => !migrations.includes(row.version))) throw new Error('Unexpected schema version');
41
+ if (command === '--check') {
42
+ const names = ['computers', 'mobile_authorizations', 'official_computers', 'announcements', 'relay_settings', 'users', 'user_login_identities'];
43
+ if (versions.some(row => row.version === '002_email_accounts')) names.push('email_login_challenges', 'user_sessions', 'email_send_limits');
44
+ if (versions.some(row => row.version === '003_relay_usage')) names.push('relay_admin_emails', 'relay_usage_settings', 'relay_usage_totals', 'relay_usage_computer_totals', 'relay_usage_entitlements', 'relay_usage_budgets', 'relay_usage_computer_budgets', 'relay_usage_sessions');
45
+ if (versions.some(row => row.version === '004_relay_usage_actions')) names.push('relay_usage_actions');
46
+ if (versions.some(row => row.version === '006_relay_diagnostics')) names.push('relay_diagnostics');
47
+ if (versions.some(row => row.version === '007_relay_usage_daily')) names.push('relay_usage_user_days', 'relay_usage_computer_days');
48
+ const counts = {};
49
+ for (const name of names) { const [rows] = await db.query(`SELECT COUNT(*) AS count FROM ${name}`); counts[name] = rows[0].count; }
50
+ if (process.env.RELAY_STATE_FILE && process.env.RELAY_ADMIN_STATE_FILE) {
51
+ const devices = deviceStore(process.env.RELAY_STATE_FILE).entries();
52
+ const admin = relayAdminState(process.env.RELAY_ADMIN_STATE_FILE, 8).read();
53
+ const expected = { computers: devices.length,
54
+ mobile_authorizations: devices.reduce((sum, item) => sum + item.clients.length, 0),
55
+ official_computers: devices.filter(item => item.officialComputer).length,
56
+ announcements: admin.announcements.length, relay_settings: 1 };
57
+ if (Object.keys(expected).some(name => counts[name] !== expected[name])) throw new Error('Imported row counts differ from JSON');
58
+ const [saved] = await db.query('SELECT id,host_hash,status,revision FROM computers');
59
+ const original = new Map(devices.map(item => [item.id, item]));
60
+ for (const row of saved) {
61
+ const item = original.get(row.id);
62
+ if (!item || item.hostHash !== row.host_hash || item.status !== row.status || item.revision !== Number(row.revision)) {
63
+ throw new Error('Imported computer identity differs from JSON');
64
+ }
65
+ }
66
+ const [authorized] = await db.query('SELECT computer_id,client_id,credential_hash FROM mobile_authorizations');
67
+ for (const row of authorized) {
68
+ const item = original.get(row.computer_id)?.clients.find(client => client.id === row.client_id);
69
+ if (!item || item.hash !== row.credential_hash) throw new Error('Imported mobile authorization differs from JSON');
70
+ }
71
+ const [settings] = await db.query('SELECT max_relay_sessions FROM relay_settings WHERE id=1');
72
+ if (settings[0]?.max_relay_sessions !== admin.maxRelaySessions) throw new Error('Imported relay setting differs from JSON');
73
+ console.log(JSON.stringify({ verified: true, counts }));
74
+ } else console.log(JSON.stringify(counts));
75
+ } else {
76
+ const stateFile = process.env.RELAY_STATE_FILE, adminFile = process.env.RELAY_ADMIN_STATE_FILE;
77
+ if (!stateFile || !adminFile) throw new Error('RELAY_STATE_FILE and RELAY_ADMIN_STATE_FILE required for import');
78
+ const devices = deviceStore(stateFile).entries();
79
+ const admin = relayAdminState(adminFile, 8).read();
80
+ const [existing] = await db.query('SELECT (SELECT COUNT(*) FROM computers) AS computers, (SELECT COUNT(*) FROM relay_settings) AS settings');
81
+ if (existing[0].computers || existing[0].settings) throw new Error('Import target is not empty');
82
+ await db.beginTransaction();
83
+ try {
84
+ for (const item of devices) {
85
+ await db.execute('INSERT INTO computers (id,host_hash,status,revision,created_at,updated_at) VALUES (?,?,?,?,?,?)',
86
+ [item.id, item.hostHash, item.status, item.revision, sqlTime(item.createdAt), sqlTime(item.updatedAt)]);
87
+ for (const client of item.clients) await db.execute(
88
+ 'INSERT INTO mobile_authorizations (computer_id,client_id,credential_hash,created_at,updated_at) VALUES (?,?,?,?,?)',
89
+ [item.id, client.id, client.hash, sqlTime(client.createdAt), sqlTime(client.updatedAt)]);
90
+ if (item.officialComputer) {
91
+ const official = item.officialComputer;
92
+ await db.execute('INSERT INTO official_computers (computer_id,environment_id,name,hostname,updated_at) VALUES (?,?,?,?,?)',
93
+ [item.id, official.environmentId, official.name, official.hostname, sqlTime(official.updatedAt)]);
94
+ }
95
+ }
96
+ await db.execute('INSERT INTO relay_settings (id,max_relay_sessions,updated_at) VALUES (1,?,UTC_TIMESTAMP(3))', [admin.maxRelaySessions]);
97
+ for (const item of admin.announcements) await db.execute(
98
+ 'INSERT INTO announcements (id,title,message,status,created_at,updated_at,published_at) VALUES (?,?,?,?,?,?,?)',
99
+ [item.id, item.title, item.message, item.status, sqlTime(item.createdAt), sqlTime(item.updatedAt),
100
+ item.publishedAt ? sqlTime(item.publishedAt) : null]);
101
+ await db.commit();
102
+ } catch (error) { await db.rollback(); throw error; }
103
+ console.log(JSON.stringify({ imported: true, computers: devices.length,
104
+ mobileAuthorizations: devices.reduce((sum, item) => sum + item.clients.length, 0),
105
+ officialComputers: devices.filter(item => item.officialComputer).length,
106
+ announcements: admin.announcements.length }));
107
+ }
108
+ }
109
+ } finally { await db.end(); }
@@ -1,46 +1,46 @@
1
- #!/usr/bin/env node
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { createHash } from 'node:crypto';
5
- import { androidReleaseStorage } from '../relay-admin/android-release-storage.mjs';
6
- import { readHarmonyConfig, hapName, validateHarmonyMetadata, checkHarmonyVersion,
7
- verifyHarmony, activateHarmony } from '../relay-admin/harmony-release.mjs';
8
-
9
- // Local PowerShell releases use the same verifier, COS objects and activation as GitHub CD.
10
- const directory = path.resolve(process.argv[2] || '');
11
- if (!process.argv[2]) throw Error('Usage: publish-harmony-release.mjs DIRECTORY');
12
- let stage = 'metadata';
13
- try {
14
- const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'manifest.json5'), 'utf8')).app;
15
- const config = readHarmonyConfig();
16
- const metadata = { packageName: manifest.bundleName, versionCode: manifest.versionCode,
17
- versionName: manifest.versionName, certificateSha256: config.certificateSha256 };
18
- if (!Number.isSafeInteger(metadata.versionCode) || metadata.versionCode <= 0
19
- || !/^\d+\.\d+\.\d+$/.test(metadata.versionName)) throw Error();
20
- metadata.files = [hapName(metadata), 'manifest.json5', 'icon.png'].map(name => {
21
- const bytes = fs.readFileSync(path.join(directory, name));
22
- return { name, size: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex'),
23
- contentMd5: createHash('md5').update(bytes).digest('base64') };
24
- });
25
- Object.assign(metadata, { size: metadata.files[0].size, sha256: metadata.files[0].sha256,
26
- contentMd5: metadata.files[0].contentMd5 });
27
- validateHarmonyMetadata(metadata, config);
28
- checkHarmonyVersion(metadata);
29
- stage = 'signature';
30
- await verifyHarmony(directory, metadata, config);
31
- const storage = androidReleaseStorage('/etc/codex-relay/android-releases.json', '/etc/codex-relay/tencent-monitor.json');
32
- const keys = metadata.files.map(f => `code-relax/harmony/${metadata.versionCode}/${f.name}`);
33
- stage = 'upload';
34
- for (const [i, file] of metadata.files.entries()) {
35
- await new Promise((resolve, reject) => storage.cos.putObject({ ...storage.object({ objectKey: keys[i] }),
36
- Body: fs.createReadStream(path.join(directory, file.name)), ContentLength: file.size,
37
- ContentMD5: file.contentMd5, ContentType: i === 1 ? 'application/json' : i === 2 ? 'image/png' : 'application/octet-stream',
38
- Headers: { 'x-cos-forbid-overwrite': 'true' }
39
- }, (error, result) => error ? reject(error) : resolve(result)));
40
- }
41
- stage = 'publication';
42
- console.log(JSON.stringify(await activateHarmony(metadata, storage, keys)));
43
- } catch {
44
- console.error(`Harmony publication failed at ${stage}; inspect objects and current version before retrying.`);
45
- process.exitCode = 1;
46
- }
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import { createHash } from 'node:crypto';
5
+ import { androidReleaseStorage } from '../relay-admin/android-release-storage.mjs';
6
+ import { readHarmonyConfig, hapName, validateHarmonyMetadata, checkHarmonyVersion,
7
+ verifyHarmony, activateHarmony } from '../relay-admin/harmony-release.mjs';
8
+
9
+ // Local PowerShell releases use the same verifier, COS objects and activation as GitHub CD.
10
+ const directory = path.resolve(process.argv[2] || '');
11
+ if (!process.argv[2]) throw Error('Usage: publish-harmony-release.mjs DIRECTORY');
12
+ let stage = 'metadata';
13
+ try {
14
+ const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'manifest.json5'), 'utf8')).app;
15
+ const config = readHarmonyConfig();
16
+ const metadata = { packageName: manifest.bundleName, versionCode: manifest.versionCode,
17
+ versionName: manifest.versionName, certificateSha256: config.certificateSha256 };
18
+ if (!Number.isSafeInteger(metadata.versionCode) || metadata.versionCode <= 0
19
+ || !/^\d+\.\d+\.\d+$/.test(metadata.versionName)) throw Error();
20
+ metadata.files = [hapName(metadata), 'manifest.json5', 'icon.png'].map(name => {
21
+ const bytes = fs.readFileSync(path.join(directory, name));
22
+ return { name, size: bytes.length, sha256: createHash('sha256').update(bytes).digest('hex'),
23
+ contentMd5: createHash('md5').update(bytes).digest('base64') };
24
+ });
25
+ Object.assign(metadata, { size: metadata.files[0].size, sha256: metadata.files[0].sha256,
26
+ contentMd5: metadata.files[0].contentMd5 });
27
+ validateHarmonyMetadata(metadata, config);
28
+ checkHarmonyVersion(metadata);
29
+ stage = 'signature';
30
+ await verifyHarmony(directory, metadata, config);
31
+ const storage = androidReleaseStorage('/etc/codex-relay/android-releases.json', '/etc/codex-relay/tencent-monitor.json');
32
+ const keys = metadata.files.map(f => `code-relax/harmony/${metadata.versionCode}/${f.name}`);
33
+ stage = 'upload';
34
+ for (const [i, file] of metadata.files.entries()) {
35
+ await new Promise((resolve, reject) => storage.cos.putObject({ ...storage.object({ objectKey: keys[i] }),
36
+ Body: fs.createReadStream(path.join(directory, file.name)), ContentLength: file.size,
37
+ ContentMD5: file.contentMd5, ContentType: i === 1 ? 'application/json' : i === 2 ? 'image/png' : 'application/octet-stream',
38
+ Headers: { 'x-cos-forbid-overwrite': 'true' }
39
+ }, (error, result) => error ? reject(error) : resolve(result)));
40
+ }
41
+ stage = 'publication';
42
+ console.log(JSON.stringify(await activateHarmony(metadata, storage, keys)));
43
+ } catch {
44
+ console.error(`Harmony publication failed at ${stage}; inspect objects and current version before retrying.`);
45
+ process.exitCode = 1;
46
+ }
@@ -1,8 +1,8 @@
1
- import { startAdminServer } from '../src/self-relay/admin-server.mjs';
2
-
3
- const port = Number(process.env.RELAY_ADMIN_PORT || 45851);
4
- if (!Number.isSafeInteger(port) || port < 1 || port > 65535) throw new Error('Invalid RELAY_ADMIN_PORT');
5
- const server = await startAdminServer({ port });
6
- console.log(JSON.stringify({ service: 'codex-relay-admin', port: server.port }));
7
- await new Promise(resolve => { process.once('SIGTERM', resolve); process.once('SIGINT', resolve); });
8
- await server.close();
1
+ import { startAdminServer } from '../src/self-relay/admin-server.mjs';
2
+
3
+ const port = Number(process.env.RELAY_ADMIN_PORT || 45851);
4
+ if (!Number.isSafeInteger(port) || port < 1 || port > 65535) throw new Error('Invalid RELAY_ADMIN_PORT');
5
+ const server = await startAdminServer({ port });
6
+ console.log(JSON.stringify({ service: 'codex-relay-admin', port: server.port }));
7
+ await new Promise(resolve => { process.once('SIGTERM', resolve); process.once('SIGINT', resolve); });
8
+ await server.close();
@@ -1,33 +1,33 @@
1
- #!/usr/bin/env node
2
- import { startRelayDemo } from '../src/self-relay/demo.mjs';
3
- import fs from 'node:fs';
4
- import { spawnSync } from 'node:child_process';
5
-
6
- const mode = 'lan-shared';
7
- let remoteRelay;
8
- if (process.env.RELAY_URL) {
9
- let hostKey;
10
- const sshIndex = process.argv.indexOf('--ssh-host');
11
- if (sshIndex !== -1) {
12
- const host = process.argv[sshIndex + 1];
13
- if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(host || '')) throw new Error('Invalid SSH alias');
14
- const result = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', host,
15
- 'sudo -n cat /etc/codex-relay/host.key'], { encoding: 'utf8', timeout: 15000, windowsHide: true });
16
- if (result.error || result.status !== 0) throw new Error('Cannot read relay credential through SSH');
17
- hostKey = result.stdout.trim();
18
- } else hostKey = fs.readFileSync(process.env.RELAY_HOST_KEY_FILE, 'utf8').trim();
19
- remoteRelay = { url: process.env.RELAY_URL, hostKey };
20
- }
21
- const demo = await startRelayDemo({ mode, remoteRelay });
22
- let client;
23
- try {
24
- client = demo.connect(); await client.ready;
25
- const health = await client.request('/api/health');
26
- if (health.status !== 200 || health.body.transportMode !== mode) throw new Error('Relay validation failed');
27
- console.log(JSON.stringify({ ok: true, simulated: true, mode, relay: demo.relay.url,
28
- bridge: demo.bridgeUrl, message: '中继 → 连接器 → 模拟桌面服务已连通;未连接 Codex,未执行任务。' }, null, 2));
29
- if (!process.argv.includes('--check')) {
30
- console.log('验证连接保持运行。Ctrl+C 停止全部模拟进程内服务。');
31
- await new Promise(resolve => { process.once('SIGINT', resolve); process.once('SIGTERM', resolve); });
32
- }
33
- } finally { client?.close(); await demo.close(); }
1
+ #!/usr/bin/env node
2
+ import { startRelayDemo } from '../src/self-relay/demo.mjs';
3
+ import fs from 'node:fs';
4
+ import { spawnSync } from 'node:child_process';
5
+
6
+ const mode = 'lan-shared';
7
+ let remoteRelay;
8
+ if (process.env.RELAY_URL) {
9
+ let hostKey;
10
+ const sshIndex = process.argv.indexOf('--ssh-host');
11
+ if (sshIndex !== -1) {
12
+ const host = process.argv[sshIndex + 1];
13
+ if (!/^[a-zA-Z0-9][a-zA-Z0-9._-]*$/.test(host || '')) throw new Error('Invalid SSH alias');
14
+ const result = spawnSync('ssh', ['-o', 'BatchMode=yes', '-o', 'ConnectTimeout=10', host,
15
+ 'sudo -n cat /etc/codex-relay/host.key'], { encoding: 'utf8', timeout: 15000, windowsHide: true });
16
+ if (result.error || result.status !== 0) throw new Error('Cannot read relay credential through SSH');
17
+ hostKey = result.stdout.trim();
18
+ } else hostKey = fs.readFileSync(process.env.RELAY_HOST_KEY_FILE, 'utf8').trim();
19
+ remoteRelay = { url: process.env.RELAY_URL, hostKey };
20
+ }
21
+ const demo = await startRelayDemo({ mode, remoteRelay });
22
+ let client;
23
+ try {
24
+ client = demo.connect(); await client.ready;
25
+ const health = await client.request('/api/health');
26
+ if (health.status !== 200 || health.body.transportMode !== mode) throw new Error('Relay validation failed');
27
+ console.log(JSON.stringify({ ok: true, simulated: true, mode, relay: demo.relay.url,
28
+ bridge: demo.bridgeUrl, message: '中继 → 连接器 → 模拟桌面服务已连通;未连接 Codex,未执行任务。' }, null, 2));
29
+ if (!process.argv.includes('--check')) {
30
+ console.log('验证连接保持运行。Ctrl+C 停止全部模拟进程内服务。');
31
+ await new Promise(resolve => { process.once('SIGINT', resolve); process.once('SIGTERM', resolve); });
32
+ }
33
+ } finally { client?.close(); await demo.close(); }
@@ -1,66 +1,66 @@
1
- #!/usr/bin/env node
2
- import fs from 'node:fs';
3
- import serviceEndpoints from '../shared/service-endpoints.cjs';
4
- import { deviceStore, validId } from '../src/self-relay/device-store.mjs';
5
- import { openRelayMysql } from '../src/self-relay/mysql-state.mjs';
6
- import { startSelfRelay } from '../src/self-relay/server.mjs';
7
- import { updateDebugAllowlist } from '../src/self-relay/debug-allowlist.mjs';
8
-
9
- const keyFile = process.env.RELAY_HOST_KEY_FILE;
10
- if (!keyFile) throw new Error('RELAY_HOST_KEY_FILE is required');
11
- const hostKey = fs.readFileSync(keyFile, 'utf8').trim();
12
- const port = Number(process.env.RELAY_PORT || 45850);
13
- if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error('Invalid RELAY_PORT');
14
- const stateFile = process.env.RELAY_STATE_FILE;
15
- const mysqlConfigFile = process.env.RELAY_MYSQL_CONFIG_FILE;
16
- const accountConfigFile = process.env.RELAY_ACCOUNT_CONFIG_FILE;
17
- const longPairingFile = process.env.RELAY_LONG_PAIRING_FILE || '/var/lib/codex-relay/long-pairing.json';
18
- if (!stateFile && !mysqlConfigFile) throw new Error('RELAY_STATE_FILE or RELAY_MYSQL_CONFIG_FILE is required');
19
- const debugAllowlistFile = process.env.RELAY_DEBUG_ALLOWLIST_FILE || '/etc/codex-relay/debug-allowlist.json';
20
- const adminStateFile = process.env.RELAY_ADMIN_STATE_FILE || '/var/lib/codex-relay/admin.json';
21
- const maxRelaySessions = Number(process.env.RELAY_MAX_RELAY_SESSIONS || 8);
22
- if (!Number.isSafeInteger(maxRelaySessions) || maxRelaySessions < 1 || maxRelaySessions > 256) throw new Error('Invalid RELAY_MAX_RELAY_SESSIONS');
23
- const debugCommand = process.argv.find(value => ['--debug-list', '--debug-allow', '--debug-deny'].includes(value));
24
- if (debugCommand) {
25
- const database = mysqlConfigFile ? await openRelayMysql(mysqlConfigFile, maxRelaySessions) : null;
26
- const records = (database?.devices || deviceStore(stateFile)).entries();
27
- if (debugCommand === '--debug-list') {
28
- console.log(JSON.stringify(records.map(record => ({
29
- deviceId: record.id, status: record.status, clientCount: record.clients.length,
30
- })), null, 2));
31
- } else {
32
- const index = process.argv.indexOf(debugCommand), deviceId = process.argv[index + 1];
33
- if (!validId(deviceId)) throw new Error('Invalid device ID');
34
- const record = records.find(record => record.id === deviceId && record.status === 'active');
35
- if (!record && debugCommand === '--debug-allow') throw new Error('Active paired device not found');
36
- updateDebugAllowlist(debugAllowlistFile, deviceId, debugCommand === '--debug-allow');
37
- console.log('Debug allowlist updated; restart relay to apply.');
38
- }
39
- if (database) await database.close();
40
- process.exit(0);
41
- }
42
- if (process.argv.includes('--init-state')) { if (mysqlConfigFile) throw new Error('Use migrations for MySQL mode'); deviceStore(stateFile, { initialize: true }); console.log('Device store initialized'); process.exit(0); }
43
- const revokeIndex = process.argv.indexOf('--revoke-host');
44
- if (revokeIndex >= 0) {
45
- // Run only with the service stopped: this is an offline administrator operation.
46
- if (!process.argv.includes('--offline')) throw new Error('Stop the relay service and supply --offline');
47
- const id = process.argv[revokeIndex + 1];
48
- if (!validId(id)) throw new Error('Invalid device ID');
49
- const database = mysqlConfigFile ? await openRelayMysql(mysqlConfigFile, maxRelaySessions) : null;
50
- const store = database?.devices || deviceStore(stateFile), record = store.entries().find(item => item.id === id);
51
- if (!record) throw new Error('Device not found');
52
- await store.save({ ...record, status: 'revoked', clients: [], revision: record.revision + 1, updatedAt: new Date().toISOString() });
53
- if (database) await database.close();
54
- console.log('Computer and phone authorization revoked'); process.exit(0);
55
- }
56
- const relay = await startSelfRelay({ port, hostKey, stateFile, mysqlConfigFile, accountConfigFile, debugAllowlistFile, adminStateFile, maxRelaySessions,
57
- longPairingFile,
58
- androidReleaseConfigFile: process.env.RELAY_ANDROID_RELEASE_CONFIG_FILE,
59
- androidReleaseCredentialFile: process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/tencent-monitor.json` : undefined,
60
- usageDiagnosticKeyFile: process.env.RELAY_USAGE_DIAGNOSTIC_KEY_FILE,
61
- publicRelayUrl: process.env.RELAY_PUBLIC_URL || serviceEndpoints.relayUrl, appDownloadUrl: process.env.RELAY_APP_DOWNLOAD_URL || serviceEndpoints.installUrl,
62
- log: entry => console.log(JSON.stringify({ time: new Date().toISOString(), ...entry })) });
63
- console.log(JSON.stringify({ service: 'codex-self-relay', listen: relay.url, maxConnections: 128, maxRelaySessions,
64
- maxFrameMiB: 8, encryption: 'TLS must be supplied by reverse proxy; no end-to-end encryption' }));
65
- await new Promise(resolve => { process.once('SIGTERM', resolve); process.once('SIGINT', resolve); });
66
- await relay.close();
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import serviceEndpoints from '../shared/service-endpoints.cjs';
4
+ import { deviceStore, validId } from '../src/self-relay/device-store.mjs';
5
+ import { openRelayMysql } from '../src/self-relay/mysql-state.mjs';
6
+ import { startSelfRelay } from '../src/self-relay/server.mjs';
7
+ import { updateDebugAllowlist } from '../src/self-relay/debug-allowlist.mjs';
8
+
9
+ const keyFile = process.env.RELAY_HOST_KEY_FILE;
10
+ if (!keyFile) throw new Error('RELAY_HOST_KEY_FILE is required');
11
+ const hostKey = fs.readFileSync(keyFile, 'utf8').trim();
12
+ const port = Number(process.env.RELAY_PORT || 45850);
13
+ if (!Number.isInteger(port) || port < 1024 || port > 65535) throw new Error('Invalid RELAY_PORT');
14
+ const stateFile = process.env.RELAY_STATE_FILE;
15
+ const mysqlConfigFile = process.env.RELAY_MYSQL_CONFIG_FILE;
16
+ const accountConfigFile = process.env.RELAY_ACCOUNT_CONFIG_FILE;
17
+ const longPairingFile = process.env.RELAY_LONG_PAIRING_FILE || '/var/lib/codex-relay/long-pairing.json';
18
+ if (!stateFile && !mysqlConfigFile) throw new Error('RELAY_STATE_FILE or RELAY_MYSQL_CONFIG_FILE is required');
19
+ const debugAllowlistFile = process.env.RELAY_DEBUG_ALLOWLIST_FILE || '/etc/codex-relay/debug-allowlist.json';
20
+ const adminStateFile = process.env.RELAY_ADMIN_STATE_FILE || '/var/lib/codex-relay/admin.json';
21
+ const maxRelaySessions = Number(process.env.RELAY_MAX_RELAY_SESSIONS || 8);
22
+ if (!Number.isSafeInteger(maxRelaySessions) || maxRelaySessions < 1 || maxRelaySessions > 256) throw new Error('Invalid RELAY_MAX_RELAY_SESSIONS');
23
+ const debugCommand = process.argv.find(value => ['--debug-list', '--debug-allow', '--debug-deny'].includes(value));
24
+ if (debugCommand) {
25
+ const database = mysqlConfigFile ? await openRelayMysql(mysqlConfigFile, maxRelaySessions) : null;
26
+ const records = (database?.devices || deviceStore(stateFile)).entries();
27
+ if (debugCommand === '--debug-list') {
28
+ console.log(JSON.stringify(records.map(record => ({
29
+ deviceId: record.id, status: record.status, clientCount: record.clients.length,
30
+ })), null, 2));
31
+ } else {
32
+ const index = process.argv.indexOf(debugCommand), deviceId = process.argv[index + 1];
33
+ if (!validId(deviceId)) throw new Error('Invalid device ID');
34
+ const record = records.find(record => record.id === deviceId && record.status === 'active');
35
+ if (!record && debugCommand === '--debug-allow') throw new Error('Active paired device not found');
36
+ updateDebugAllowlist(debugAllowlistFile, deviceId, debugCommand === '--debug-allow');
37
+ console.log('Debug allowlist updated; restart relay to apply.');
38
+ }
39
+ if (database) await database.close();
40
+ process.exit(0);
41
+ }
42
+ if (process.argv.includes('--init-state')) { if (mysqlConfigFile) throw new Error('Use migrations for MySQL mode'); deviceStore(stateFile, { initialize: true }); console.log('Device store initialized'); process.exit(0); }
43
+ const revokeIndex = process.argv.indexOf('--revoke-host');
44
+ if (revokeIndex >= 0) {
45
+ // Run only with the service stopped: this is an offline administrator operation.
46
+ if (!process.argv.includes('--offline')) throw new Error('Stop the relay service and supply --offline');
47
+ const id = process.argv[revokeIndex + 1];
48
+ if (!validId(id)) throw new Error('Invalid device ID');
49
+ const database = mysqlConfigFile ? await openRelayMysql(mysqlConfigFile, maxRelaySessions) : null;
50
+ const store = database?.devices || deviceStore(stateFile), record = store.entries().find(item => item.id === id);
51
+ if (!record) throw new Error('Device not found');
52
+ await store.save({ ...record, status: 'revoked', clients: [], revision: record.revision + 1, updatedAt: new Date().toISOString() });
53
+ if (database) await database.close();
54
+ console.log('Computer and phone authorization revoked'); process.exit(0);
55
+ }
56
+ const relay = await startSelfRelay({ port, hostKey, stateFile, mysqlConfigFile, accountConfigFile, debugAllowlistFile, adminStateFile, maxRelaySessions,
57
+ longPairingFile,
58
+ androidReleaseConfigFile: process.env.RELAY_ANDROID_RELEASE_CONFIG_FILE,
59
+ androidReleaseCredentialFile: process.env.CREDENTIALS_DIRECTORY ? `${process.env.CREDENTIALS_DIRECTORY}/tencent-monitor.json` : undefined,
60
+ usageDiagnosticKeyFile: process.env.RELAY_USAGE_DIAGNOSTIC_KEY_FILE,
61
+ publicRelayUrl: process.env.RELAY_PUBLIC_URL || serviceEndpoints.relayUrl, appDownloadUrl: process.env.RELAY_APP_DOWNLOAD_URL || serviceEndpoints.installUrl,
62
+ log: entry => console.log(JSON.stringify({ time: new Date().toISOString(), ...entry })) });
63
+ console.log(JSON.stringify({ service: 'codex-self-relay', listen: relay.url, maxConnections: 128, maxRelaySessions,
64
+ maxFrameMiB: 8, encryption: 'TLS must be supplied by reverse proxy; no end-to-end encryption' }));
65
+ await new Promise(resolve => { process.once('SIGTERM', resolve); process.once('SIGINT', resolve); });
66
+ await relay.close();
@@ -1,11 +1,11 @@
1
- import fs from 'node:fs';
2
- import { randomBytes } from 'node:crypto';
3
- import { passwordRecord } from '../src/self-relay/admin-auth.mjs';
4
-
5
- // Run as the management service user (or root and then chown root:codexrelay).
6
- // The plaintext is printed once for handoff; only its salted scrypt hash is saved.
7
- const file = process.argv[2] || '/etc/codex-relay/admin-password.json';
8
- const password = randomBytes(18).toString('base64url');
9
- const record = await passwordRecord(password);
10
- fs.writeFileSync(file, JSON.stringify(record) + '\n', { mode: 0o640, flag: 'wx' });
11
- console.log(password);
1
+ import fs from 'node:fs';
2
+ import { randomBytes } from 'node:crypto';
3
+ import { passwordRecord } from '../src/self-relay/admin-auth.mjs';
4
+
5
+ // Run as the management service user (or root and then chown root:codexrelay).
6
+ // The plaintext is printed once for handoff; only its salted scrypt hash is saved.
7
+ const file = process.argv[2] || '/etc/codex-relay/admin-password.json';
8
+ const password = randomBytes(18).toString('base64url');
9
+ const record = await passwordRecord(password);
10
+ fs.writeFileSync(file, JSON.stringify(record) + '\n', { mode: 0o640, flag: 'wx' });
11
+ console.log(password);
@@ -1 +1 @@
1
- {"layout":"npm-platform-packages","version":"0.1.0-beta.68"}
1
+ {"layout":"npm-platform-packages","version":"0.1.0-beta.69"}