@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 +1 -1
- package/src/commands/panel.js +59 -40
- package/src/commands/plugin.js +23 -159
- package/src/commands/setup.js +31 -76
- package/src/commands/update.js +30 -0
- package/src/index.js +1 -1
- package/src/lib/agent-plugin-router.js +141 -0
- package/src/lib/agent-plugins.js +358 -0
- package/src/lib/cert-store.js +20 -54
- package/src/lib/keychain.js +0 -154
- package/src/lib/local-plugin-host.js +7 -2
- package/src/lib/panel-api-routes.js +130 -0
- package/src/lib/panel-server.js +55 -0
- package/src/lib/panel-service.js +3 -1
package/package.json
CHANGED
package/src/commands/panel.js
CHANGED
|
@@ -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({
|
|
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
|
-
|
|
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.
|
|
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
|
-
//
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
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
|
|
package/src/commands/plugin.js
CHANGED
|
@@ -1,46 +1,12 @@
|
|
|
1
1
|
import chalk from 'chalk';
|
|
2
|
-
import {
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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
|
|
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(`
|
|
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
|
-
|
|
125
|
-
const
|
|
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
|
|
45
|
+
await uninstallAgentPlugin(label, plugin.name);
|
|
46
|
+
console.log(chalk.green(` Plugin "${plugin.name}" uninstalled`));
|
|
145
47
|
} catch (err) {
|
|
146
|
-
console.
|
|
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
|
-
|
|
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
|
-
|
|
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(`
|
|
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
|
|
74
|
+
const registry = await readAgentPluginRegistry(label);
|
|
211
75
|
const b = chalk.bold;
|
|
212
76
|
const c = chalk.cyan;
|
|
213
77
|
const d = chalk.dim;
|
package/src/commands/setup.js
CHANGED
|
@@ -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(
|
|
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: '
|
|
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
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
401
|
-
p12Path:
|
|
402
|
-
keychainIdentity:
|
|
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:
|
|
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
|
-
|
|
526
|
-
|
|
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.
|
|
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.
|
|
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:
|
|
643
|
-
p12Path:
|
|
644
|
-
keychainIdentity:
|
|
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:
|
|
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(
|
|
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'));
|