@lamalibre/portlama-agent 1.0.20 → 1.0.22

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": "@lamalibre/portlama-agent",
3
- "version": "1.0.20",
3
+ "version": "1.0.22",
4
4
  "description": "Tunnel agent for Portlama — manages Chisel tunnel client as a system service",
5
5
  "type": "module",
6
6
  "license": "SEE LICENSE IN LICENSE.md",
@@ -24,9 +24,9 @@ const DEFAULT_PANEL_PORT = 9393;
24
24
 
25
25
  /**
26
26
  * @param {string[]} args
27
- * @param {{ label: string }} options
27
+ * @param {{ label: string, json?: boolean }} options
28
28
  */
29
- export async function runPanel(args, { label }) {
29
+ export async function runPanel(args, { label, json: globalJson = false }) {
30
30
  assertSupportedPlatform();
31
31
 
32
32
  const config = await loadAgentConfig(label);
@@ -35,9 +35,10 @@ export async function runPanel(args, { label }) {
35
35
  process.exit(1);
36
36
  }
37
37
 
38
- const isJson = args.includes('--json');
38
+ const isJson = globalJson || args.includes('--json');
39
39
  const isEnable = args.includes('--enable');
40
40
  const isDisable = args.includes('--disable');
41
+ const isLocalOnly = args.includes('--local-only');
41
42
  const isStatus = args.includes('--status') || (!isEnable && !isDisable);
42
43
 
43
44
  // Parse --port flag
@@ -51,24 +52,24 @@ export async function runPanel(args, { label }) {
51
52
  }
52
53
 
53
54
  if (isEnable) {
54
- await enablePanel(label, config, port, isJson);
55
+ await enablePanel(label, config, port, isJson, isLocalOnly);
55
56
  } else if (isDisable) {
56
- await disablePanel(label, config, isJson);
57
+ await disablePanel(label, config, isJson, isLocalOnly);
57
58
  } else if (isStatus) {
58
59
  await showStatus(label, config, isJson);
59
60
  }
60
61
  }
61
62
 
62
- async function enablePanel(label, config, port, isJson) {
63
+ async function enablePanel(label, config, port, isJson, localOnly = false) {
63
64
  // 1. Check if already enabled
64
65
  const loaded = await isPanelLoaded(label);
65
66
  if (loaded) {
66
67
  if (isJson) {
67
- console.log(JSON.stringify({ error: 'Panel service already running' }));
68
+ console.log(JSON.stringify({ ok: true, alreadyRunning: true, port: config.panelPort || port }));
68
69
  } else {
69
70
  console.log(chalk.yellow('Panel service is already running.'));
70
71
  }
71
- process.exit(1);
72
+ return;
72
73
  }
73
74
 
74
75
  // 2. Generate and write service config
@@ -78,7 +79,24 @@ async function enablePanel(label, config, port, isJson) {
78
79
  // 3. Start the panel service
79
80
  await loadPanelService(label);
80
81
 
81
- // 4. Create the tunnel on the panel server
82
+ // 4. Save panel config
83
+ config.panelPort = port;
84
+ config.panelEnabled = true;
85
+ config.updatedAt = new Date().toISOString();
86
+ await saveAgentConfig(label, config);
87
+
88
+ // 5. If local-only, skip tunnel exposure and chisel restart
89
+ if (localOnly) {
90
+ if (isJson) {
91
+ console.log(JSON.stringify({ ok: true, port }));
92
+ } else {
93
+ console.log(chalk.green('\nAgent panel started locally.'));
94
+ console.log(` Port: ${chalk.cyan(port)}`);
95
+ }
96
+ return;
97
+ }
98
+
99
+ // 6. Create the tunnel on the panel server
82
100
  let tunnel;
83
101
  try {
84
102
  const result = await exposePanelTunnel(config, port);
@@ -87,6 +105,9 @@ async function enablePanel(label, config, port, isJson) {
87
105
  // Rollback: stop the panel service we just started
88
106
  await unloadPanelService(label);
89
107
  await removePanelServiceConfig(label);
108
+ config.panelEnabled = false;
109
+ delete config.panelPort;
110
+ await saveAgentConfig(label, config);
90
111
  const msg = err instanceof Error ? err.message : 'Unknown error';
91
112
  if (isJson) {
92
113
  console.log(JSON.stringify({ error: msg }));
@@ -96,13 +117,7 @@ async function enablePanel(label, config, port, isJson) {
96
117
  process.exit(1);
97
118
  }
98
119
 
99
- // 5. Save panel config
100
- config.panelPort = port;
101
- config.panelEnabled = true;
102
- config.updatedAt = new Date().toISOString();
103
- await saveAgentConfig(label, config);
104
-
105
- // 6. Update the agent's chisel service (needs new tunnel mapping)
120
+ // 7. Update the agent's chisel service (needs new tunnel mapping)
106
121
  try {
107
122
  const { fetchAgentConfig } = await import('../lib/panel-api.js');
108
123
  const { generateServiceConfig, writeServiceConfigFile } =
@@ -132,14 +147,16 @@ async function enablePanel(label, config, port, isJson) {
132
147
  }
133
148
  }
134
149
 
135
- async function disablePanel(label, config, isJson) {
136
- // 1. Retract the tunnel
137
- try {
138
- await retractPanelTunnel(config);
139
- } catch (err) {
140
- if (!isJson) {
141
- const msg = err instanceof Error ? err.message : 'Unknown error';
142
- console.log(chalk.yellow(`Warning: Could not retract panel tunnel: ${msg}`));
150
+ async function disablePanel(label, config, isJson, localOnly = false) {
151
+ // 1. Retract the tunnel (skip in local-only mode)
152
+ if (!localOnly) {
153
+ try {
154
+ await retractPanelTunnel(config);
155
+ } catch (err) {
156
+ if (!isJson) {
157
+ const msg = err instanceof Error ? err.message : 'Unknown error';
158
+ console.log(chalk.yellow(`Warning: Could not retract panel tunnel: ${msg}`));
159
+ }
143
160
  }
144
161
  }
145
162
 
@@ -149,21 +166,23 @@ async function disablePanel(label, config, isJson) {
149
166
  // 3. Remove service config
150
167
  await removePanelServiceConfig(label);
151
168
 
152
- // 4. Update chisel (remove the panel tunnel mapping)
153
- try {
154
- const { fetchAgentConfig } = await import('../lib/panel-api.js');
155
- const { generateServiceConfig, writeServiceConfigFile } =
156
- await import('../lib/service-config.js');
157
- const { unloadAgent, loadAgent } = await import('../lib/service.js');
158
- const agentConfig = await fetchAgentConfig(config);
159
- const serviceContent = generateServiceConfig(agentConfig.chiselArgs, label);
160
- await writeServiceConfigFile(serviceContent, label);
161
- await unloadAgent(label);
162
- await loadAgent(label);
163
- } catch (err) {
164
- if (!isJson) {
165
- const msg = err instanceof Error ? err.message : 'Unknown error';
166
- console.log(chalk.yellow(`Warning: Could not restart chisel: ${msg}`));
169
+ // 4. Update chisel (remove the panel tunnel mapping) — skip in local-only mode
170
+ if (!localOnly) {
171
+ try {
172
+ const { fetchAgentConfig } = await import('../lib/panel-api.js');
173
+ const { generateServiceConfig, writeServiceConfigFile } =
174
+ await import('../lib/service-config.js');
175
+ const { unloadAgent, loadAgent } = await import('../lib/service.js');
176
+ const agentConfig = await fetchAgentConfig(config);
177
+ const serviceContent = generateServiceConfig(agentConfig.chiselArgs, label);
178
+ await writeServiceConfigFile(serviceContent, label);
179
+ await unloadAgent(label);
180
+ await loadAgent(label);
181
+ } catch (err) {
182
+ if (!isJson) {
183
+ const msg = err instanceof Error ? err.message : 'Unknown error';
184
+ console.log(chalk.yellow(`Warning: Could not restart chisel: ${msg}`));
185
+ }
167
186
  }
168
187
  }
169
188
 
@@ -176,7 +195,7 @@ async function disablePanel(label, config, isJson) {
176
195
  if (isJson) {
177
196
  console.log(JSON.stringify({ ok: true }));
178
197
  } else {
179
- console.log(chalk.green('Agent panel retracted.'));
198
+ console.log(chalk.green(localOnly ? 'Agent panel stopped.' : 'Agent panel retracted.'));
180
199
  }
181
200
  }
182
201
 
@@ -1,46 +1,12 @@
1
1
  import chalk from 'chalk';
2
- import { execa } from 'execa';
3
- import { readFile, writeFile, rename, open, mkdir, rm } from 'node:fs/promises';
4
- import { createRequire } from 'node:module';
5
- import path from 'node:path';
6
- import { assertSupportedPlatform, agentDataDir, agentPluginsFile, agentPluginsDir } from '../lib/platform.js';
2
+ import { assertSupportedPlatform } from '../lib/platform.js';
7
3
  import { validateLabel } from '../lib/registry.js';
8
-
9
- /**
10
- * Read the local plugin registry.
11
- * @param {string} label - Agent label
12
- */
13
- async function readLocalPlugins(label) {
14
- try {
15
- const raw = await readFile(agentPluginsFile(label), 'utf-8');
16
- const parsed = JSON.parse(raw);
17
- return Array.isArray(parsed.plugins) ? parsed : { plugins: [] };
18
- } catch (err) {
19
- if (err.code === 'ENOENT') {
20
- return { plugins: [] };
21
- }
22
- throw new Error(`Failed to read local plugin registry: ${err.message}`);
23
- }
24
- }
25
-
26
- /**
27
- * Write the local plugin registry atomically.
28
- * @param {string} label - Agent label
29
- * @param {object} data
30
- */
31
- async function writeLocalPlugins(label, data) {
32
- const pluginsFile = agentPluginsFile(label);
33
- const tmpPath = `${pluginsFile}.tmp`;
34
-
35
- const content = JSON.stringify(data, null, 2) + '\n';
36
- await writeFile(tmpPath, content, { encoding: 'utf-8', mode: 0o600 });
37
-
38
- const fd = await open(tmpPath, 'r');
39
- await fd.sync();
40
- await fd.close();
41
-
42
- await rename(tmpPath, pluginsFile);
43
- }
4
+ import {
5
+ readAgentPluginRegistry,
6
+ installAgentPlugin,
7
+ uninstallAgentPlugin,
8
+ updateAgentPlugin,
9
+ } from '../lib/agent-plugins.js';
44
10
 
45
11
  /**
46
12
  * Install a plugin locally on the agent.
@@ -48,71 +14,14 @@ async function writeLocalPlugins(label, data) {
48
14
  * @param {string} packageName
49
15
  */
50
16
  async function installLocal(label, packageName) {
51
- if (!packageName.startsWith('@lamalibre/')) {
52
- console.error(chalk.red(' Only @lamalibre/ scoped packages are allowed'));
53
- process.exit(1);
54
- }
55
-
56
- const registry = await readLocalPlugins(label);
57
- const existing = registry.plugins.find((p) => p.packageName === packageName);
58
- if (existing) {
59
- console.log(chalk.yellow(` Plugin "${packageName}" is already installed`));
60
- return;
61
- }
62
-
63
17
  console.log(chalk.cyan(` Installing ${packageName}...`));
64
-
65
- // Ensure agent dir has a package.json so npm installs locally
66
- const agentDir = agentDataDir(label);
67
- await mkdir(agentDir, { recursive: true, mode: 0o700 });
68
- const agentPkgJson = path.join(agentDir, 'package.json');
69
- try {
70
- await readFile(agentPkgJson, 'utf-8');
71
- } catch (err) {
72
- if (err.code === 'ENOENT') {
73
- await writeFile(agentPkgJson, '{"private":true}\n', { encoding: 'utf-8', mode: 0o600 });
74
- }
75
- }
76
-
77
18
  try {
78
- await execa('npm', ['install', '--ignore-scripts', packageName], { cwd: agentDir, stdio: 'inherit' });
19
+ const entry = await installAgentPlugin(label, packageName);
20
+ console.log(chalk.green(` Plugin "${entry.name}" installed`));
79
21
  } catch (err) {
80
- console.error(chalk.red(` Failed to install: ${err.message}`));
81
- process.exit(1);
82
- }
83
-
84
- // Read the plugin manifest
85
- let manifest;
86
- try {
87
- const require = createRequire(path.join(agentDir, '/'));
88
- const manifestPath = require.resolve(`${packageName}/portlama-plugin.json`);
89
- const manifestRaw = await readFile(manifestPath, 'utf-8');
90
- manifest = JSON.parse(manifestRaw);
91
- } catch {
92
- console.log(chalk.yellow(' Warning: No portlama-plugin.json found — registering with package name only'));
93
- manifest = { name: packageName.replace('@lamalibre/', ''), version: 'unknown' };
94
- }
95
-
96
- // Validate manifest name to prevent path traversal
97
- if (!/^[a-z0-9-]+$/.test(manifest.name)) {
98
- console.error(chalk.red(` Invalid plugin name: "${manifest.name}"`));
22
+ console.error(chalk.red(` ${err.message}`));
99
23
  process.exit(1);
100
24
  }
101
-
102
- // Create local plugin directory
103
- const pluginDir = path.join(agentPluginsDir(label), manifest.name);
104
- await mkdir(pluginDir, { recursive: true, mode: 0o700 });
105
-
106
- registry.plugins.push({
107
- name: manifest.name,
108
- packageName,
109
- version: manifest.version,
110
- installedAt: new Date().toISOString(),
111
- status: 'installed',
112
- });
113
-
114
- await writeLocalPlugins(label, registry);
115
- console.log(chalk.green(` Plugin "${manifest.name}" installed`));
116
25
  }
117
26
 
118
27
  /**
@@ -121,38 +30,24 @@ async function installLocal(label, packageName) {
121
30
  * @param {string} nameOrPackage
122
31
  */
123
32
  async function uninstallLocal(label, nameOrPackage) {
124
- const registry = await readLocalPlugins(label);
125
- const index = registry.plugins.findIndex(
33
+ // Resolve name from registry if a package name was given
34
+ const registry = await readAgentPluginRegistry(label);
35
+ const plugin = registry.plugins.find(
126
36
  (p) => p.name === nameOrPackage || p.packageName === nameOrPackage,
127
37
  );
128
-
129
- if (index === -1) {
38
+ if (!plugin) {
130
39
  console.error(chalk.red(` Plugin "${nameOrPackage}" not found`));
131
40
  process.exit(1);
132
41
  }
133
42
 
134
- const plugin = registry.plugins[index];
135
-
136
- if (!plugin.packageName.startsWith('@lamalibre/')) {
137
- console.error(chalk.red(' Registry corruption: invalid package scope'));
138
- process.exit(1);
139
- }
140
-
141
43
  console.log(chalk.cyan(` Uninstalling ${plugin.packageName}...`));
142
-
143
44
  try {
144
- await execa('npm', ['uninstall', plugin.packageName], { cwd: agentDataDir(label), stdio: 'inherit' });
45
+ await uninstallAgentPlugin(label, plugin.name);
46
+ console.log(chalk.green(` Plugin "${plugin.name}" uninstalled`));
145
47
  } catch (err) {
146
- console.log(chalk.yellow(` npm uninstall warning: ${err.message}`));
48
+ console.error(chalk.red(` ${err.message}`));
49
+ process.exit(1);
147
50
  }
148
-
149
- // Remove local plugin directory
150
- const pluginDir = path.join(agentPluginsDir(label), plugin.name);
151
- await rm(pluginDir, { recursive: true, force: true }).catch(() => {});
152
-
153
- registry.plugins.splice(index, 1);
154
- await writeLocalPlugins(label, registry);
155
- console.log(chalk.green(` Plugin "${plugin.name}" uninstalled`));
156
51
  }
157
52
 
158
53
  /**
@@ -161,45 +56,14 @@ async function uninstallLocal(label, nameOrPackage) {
161
56
  * @param {string} nameOrPackage
162
57
  */
163
58
  async function updateLocal(label, nameOrPackage) {
164
- const registry = await readLocalPlugins(label);
165
- const plugin = registry.plugins.find(
166
- (p) => p.name === nameOrPackage || p.packageName === nameOrPackage,
167
- );
168
-
169
- if (!plugin) {
170
- console.error(chalk.red(` Plugin "${nameOrPackage}" not found`));
171
- process.exit(1);
172
- }
173
-
174
- if (!plugin.packageName.startsWith('@lamalibre/')) {
175
- console.error(chalk.red(' Registry corruption: invalid package scope'));
176
- process.exit(1);
177
- }
178
-
179
- console.log(chalk.cyan(` Updating ${plugin.packageName}...`));
180
-
181
- const agentDir = agentDataDir(label);
59
+ console.log(chalk.cyan(` Updating ${nameOrPackage}...`));
182
60
  try {
183
- await execa('npm', ['install', '--ignore-scripts', plugin.packageName], { cwd: agentDir, stdio: 'inherit' });
61
+ const plugin = await updateAgentPlugin(label, nameOrPackage);
62
+ console.log(chalk.green(` Plugin "${plugin.name}" updated to v${plugin.version}`));
184
63
  } catch (err) {
185
- console.error(chalk.red(` Failed to update: ${err.message}`));
64
+ console.error(chalk.red(` ${err.message}`));
186
65
  process.exit(1);
187
66
  }
188
-
189
- // Re-read manifest to capture the updated version
190
- try {
191
- const require = createRequire(path.join(agentDir, '/'));
192
- const manifestPath = require.resolve(`${plugin.packageName}/portlama-plugin.json`);
193
- const manifestRaw = await readFile(manifestPath, 'utf-8');
194
- const manifest = JSON.parse(manifestRaw);
195
- plugin.version = manifest.version || plugin.version;
196
- } catch {
197
- // Manifest may not exist — keep existing version
198
- }
199
-
200
- plugin.updatedAt = new Date().toISOString();
201
- await writeLocalPlugins(label, registry);
202
- console.log(chalk.green(` Plugin "${plugin.name}" updated`));
203
67
  }
204
68
 
205
69
  /**
@@ -207,7 +71,7 @@ async function updateLocal(label, nameOrPackage) {
207
71
  * @param {string} label - Agent label
208
72
  */
209
73
  async function showStatus(label) {
210
- const registry = await readLocalPlugins(label);
74
+ const registry = await readAgentPluginRegistry(label);
211
75
  const b = chalk.bold;
212
76
  const c = chalk.cyan;
213
77
  const d = chalk.dim;
@@ -7,7 +7,6 @@ import { Listr } from 'listr2';
7
7
  import chalk from 'chalk';
8
8
  import {
9
9
  assertSupportedPlatform,
10
- isDarwin,
11
10
  CHISEL_BIN_DIR,
12
11
  AGENT_DIR,
13
12
  agentDataDir,
@@ -161,9 +160,7 @@ async function runTokenSetup(flags) {
161
160
 
162
161
  console.log('');
163
162
  console.log(chalk.bold(' Portlama Agent Setup (Token-Based Enrollment)'));
164
- console.log(chalk.dim(isDarwin()
165
- ? ' Connect this Mac to your Portlama server using a Keychain-bound certificate.'
166
- : ' Connect this machine to your Portlama server using a certificate.'));
163
+ console.log(chalk.dim(' Connect this machine to your Portlama server using a certificate.'));
167
164
  console.log('');
168
165
 
169
166
  let panelUrl = flags.panelUrl;
@@ -185,7 +182,6 @@ async function runTokenSetup(flags) {
185
182
  explicitLabel: flags.label,
186
183
  agentLabel: null,
187
184
  resolvedLabel: null,
188
- keychainIdentity: null,
189
185
  p12Path: null,
190
186
  p12Password: null,
191
187
  chiselVersion: null,
@@ -247,20 +243,7 @@ async function runTokenSetup(flags) {
247
243
  },
248
244
  },
249
245
  {
250
- title: 'Requesting macOS login password',
251
- skip: () => !isDarwin() && 'Not macOS',
252
- task: async (_ctx, task) => {
253
- task.output = chalk.dim('Required to authorize curl access to the Keychain certificate');
254
- ctx._keychainPassword = await prompt('macOS login password (for Keychain access)');
255
- if (!ctx._keychainPassword) {
256
- throw new Error('Keychain password is required on macOS to grant curl access to the certificate.');
257
- }
258
- task.output = 'Password received';
259
- },
260
- rendererOptions: { persistentOutput: true },
261
- },
262
- {
263
- title: isDarwin() ? 'Importing certificate into Keychain' : 'Storing certificate',
246
+ title: 'Storing certificate',
264
247
  task: async (_ctx, task) => {
265
248
  const result = await storeEnrolledCert(
266
249
  ctx._keyData.keyPath,
@@ -268,16 +251,10 @@ async function runTokenSetup(flags) {
268
251
  ctx._caCertPem,
269
252
  ctx.resolvedLabel,
270
253
  console,
271
- { keychainPassword: ctx._keychainPassword },
272
254
  );
273
- if (result.identity) {
274
- ctx.keychainIdentity = result.identity;
275
- task.output = `Identity "${result.identity}" imported (non-extractable)`;
276
- } else {
277
- ctx.p12Path = result.p12Path;
278
- ctx.p12Password = result.p12Password;
279
- task.output = `Certificate stored at ${result.p12Path}`;
280
- }
255
+ ctx.p12Path = result.p12Path;
256
+ ctx.p12Password = result.p12Password;
257
+ task.output = `Certificate stored at ${result.p12Path}`;
281
258
  },
282
259
  rendererOptions: { persistentOutput: true },
283
260
  },
@@ -291,9 +268,7 @@ async function runTokenSetup(flags) {
291
268
  {
292
269
  title: 'Verifying panel connectivity',
293
270
  task: async (_ctx, task) => {
294
- const authConfig = ctx.keychainIdentity
295
- ? { panelUrl: ctx.panelUrl, authMethod: 'keychain', keychainIdentity: ctx.keychainIdentity }
296
- : { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
271
+ const authConfig = { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
297
272
  const health = await fetchHealth(authConfig);
298
273
  task.output = `Panel is reachable (status: ${health.status || 'ok'})`;
299
274
  },
@@ -315,9 +290,7 @@ async function runTokenSetup(flags) {
315
290
  {
316
291
  title: 'Fetching tunnel configuration',
317
292
  task: async (_ctx, task) => {
318
- const authConfig = ctx.keychainIdentity
319
- ? { panelUrl: ctx.panelUrl, authMethod: 'keychain', keychainIdentity: ctx.keychainIdentity }
320
- : { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
293
+ const authConfig = { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
321
294
 
322
295
  const agentConfig = await fetchAgentConfig(authConfig);
323
296
  ctx.domain = agentConfig.domain;
@@ -376,30 +349,24 @@ async function runTokenSetup(flags) {
376
349
  task: async () => {
377
350
  const configData = {
378
351
  panelUrl: ctx.panelUrl,
352
+ authMethod: 'p12',
353
+ p12Path: ctx.p12Path,
354
+ p12Password: ctx.p12Password,
379
355
  agentLabel: ctx.agentLabel,
380
356
  domain: ctx.domain,
381
357
  chiselVersion: ctx.chiselVersion,
382
358
  setupAt: new Date().toISOString(),
383
359
  };
384
360
 
385
- if (ctx.keychainIdentity) {
386
- configData.authMethod = 'keychain';
387
- configData.keychainIdentity = ctx.keychainIdentity;
388
- } else {
389
- configData.authMethod = 'p12';
390
- configData.p12Path = ctx.p12Path;
391
- configData.p12Password = ctx.p12Password;
392
- }
393
-
394
361
  await saveAgentConfig(ctx.resolvedLabel, configData);
395
362
 
396
363
  // Add or update registry entry
397
364
  await upsertAgent({
398
365
  label: ctx.resolvedLabel,
399
366
  panelUrl: ctx.panelUrl,
400
- authMethod: configData.authMethod,
401
- p12Path: configData.p12Path || null,
402
- keychainIdentity: configData.keychainIdentity || null,
367
+ authMethod: 'p12',
368
+ p12Path: ctx.p12Path,
369
+ keychainIdentity: null,
403
370
  agentLabel: ctx.agentLabel,
404
371
  domain: ctx.domain,
405
372
  chiselVersion: ctx.chiselVersion,
@@ -454,7 +421,6 @@ async function runTokenSetupJson(flags) {
454
421
  explicitLabel: flags.label,
455
422
  agentLabel: null,
456
423
  resolvedLabel: null,
457
- keychainIdentity: null,
458
424
  p12Path: null,
459
425
  p12Password: null,
460
426
  chiselVersion: null,
@@ -513,7 +479,7 @@ async function runTokenSetupJson(flags) {
513
479
  },
514
480
  {
515
481
  key: 'import_cert',
516
- title: isDarwin() ? 'Importing certificate into Keychain' : 'Storing certificate',
482
+ title: 'Storing certificate',
517
483
  fn: async () => {
518
484
  const result = await storeEnrolledCert(
519
485
  ctx._keyData.keyPath,
@@ -522,12 +488,8 @@ async function runTokenSetupJson(flags) {
522
488
  ctx.resolvedLabel,
523
489
  { log: () => {}, warn: () => {}, error: () => {} },
524
490
  );
525
- if (result.identity) {
526
- ctx.keychainIdentity = result.identity;
527
- } else {
528
- ctx.p12Path = result.p12Path;
529
- ctx.p12Password = result.p12Password;
530
- }
491
+ ctx.p12Path = result.p12Path;
492
+ ctx.p12Password = result.p12Password;
531
493
  },
532
494
  },
533
495
  {
@@ -542,9 +504,7 @@ async function runTokenSetupJson(flags) {
542
504
  key: 'verify_connectivity',
543
505
  title: 'Verifying panel connectivity',
544
506
  fn: async () => {
545
- const authConfig = ctx.keychainIdentity
546
- ? { panelUrl: ctx.panelUrl, authMethod: 'keychain', keychainIdentity: ctx.keychainIdentity }
547
- : { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
507
+ const authConfig = { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
548
508
  await fetchHealth(authConfig);
549
509
  },
550
510
  },
@@ -560,9 +520,7 @@ async function runTokenSetupJson(flags) {
560
520
  key: 'fetch_config',
561
521
  title: 'Fetching tunnel configuration',
562
522
  fn: async () => {
563
- const authConfig = ctx.keychainIdentity
564
- ? { panelUrl: ctx.panelUrl, authMethod: 'keychain', keychainIdentity: ctx.keychainIdentity }
565
- : { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
523
+ const authConfig = { panelUrl: ctx.panelUrl, authMethod: 'p12', p12Path: ctx.p12Path, p12Password: ctx.p12Password };
566
524
 
567
525
  const agentConfig = await fetchAgentConfig(authConfig);
568
526
  ctx.domain = agentConfig.domain;
@@ -619,29 +577,23 @@ async function runTokenSetupJson(flags) {
619
577
  fn: async () => {
620
578
  const configData = {
621
579
  panelUrl: ctx.panelUrl,
580
+ authMethod: 'p12',
581
+ p12Path: ctx.p12Path,
582
+ p12Password: ctx.p12Password,
622
583
  agentLabel: ctx.agentLabel,
623
584
  domain: ctx.domain,
624
585
  chiselVersion: ctx.chiselVersion,
625
586
  setupAt: new Date().toISOString(),
626
587
  };
627
588
 
628
- if (ctx.keychainIdentity) {
629
- configData.authMethod = 'keychain';
630
- configData.keychainIdentity = ctx.keychainIdentity;
631
- } else {
632
- configData.authMethod = 'p12';
633
- configData.p12Path = ctx.p12Path;
634
- configData.p12Password = ctx.p12Password;
635
- }
636
-
637
589
  await saveAgentConfig(ctx.resolvedLabel, configData);
638
590
 
639
591
  await upsertAgent({
640
592
  label: ctx.resolvedLabel,
641
593
  panelUrl: ctx.panelUrl,
642
- authMethod: configData.authMethod,
643
- p12Path: configData.p12Path || null,
644
- keychainIdentity: configData.keychainIdentity || null,
594
+ authMethod: 'p12',
595
+ p12Path: ctx.p12Path,
596
+ keychainIdentity: null,
645
597
  agentLabel: ctx.agentLabel,
646
598
  domain: ctx.domain,
647
599
  chiselVersion: ctx.chiselVersion,
@@ -664,12 +616,17 @@ async function runTokenSetupJson(flags) {
664
616
  process.exit(1);
665
617
  }
666
618
 
619
+ // The p12Password transits via stdout pipe to the parent process (Tauri desktop app),
620
+ // which stores it in the OS credential store. Pipes are not visible in process listings.
621
+ // This is the same trust boundary as the server provisioner's SCP-based P12 transfer.
667
622
  emitJson({
668
623
  event: 'complete',
669
624
  agent: {
670
625
  label: ctx.resolvedLabel,
671
626
  panelUrl: ctx.panelUrl,
672
- authMethod: ctx.keychainIdentity ? 'keychain' : 'p12',
627
+ authMethod: 'p12',
628
+ p12Path: ctx.p12Path,
629
+ p12Password: ctx.p12Password,
673
630
  domain: ctx.domain,
674
631
  chiselVersion: ctx.chiselVersion,
675
632
  },
@@ -690,9 +647,7 @@ async function runP12Setup(options = {}) {
690
647
 
691
648
  console.log('');
692
649
  console.log(chalk.bold(' Portlama Agent Setup'));
693
- console.log(chalk.dim(isDarwin()
694
- ? ' Connect this Mac to your Portlama server.'
695
- : ' Connect this machine to your Portlama server.'));
650
+ console.log(chalk.dim(' Connect this machine to your Portlama server.'));
696
651
  console.log('');
697
652
  console.log(chalk.dim(' The admin must generate an agent certificate from the panel first:'));
698
653
  console.log(chalk.dim(' Panel → Certificates → Agent Certificates → Generate'));