@lamalibre/portlama-agent 1.0.2 → 1.0.4
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/LICENSE.md +2 -1
- package/README.md +21 -21
- package/package.json +3 -2
- package/src/commands/cp.js +196 -0
- package/src/commands/deploy.js +30 -9
- package/src/commands/setup.js +69 -23
- package/src/commands/shell-log.js +185 -0
- package/src/commands/shell-server.js +491 -0
- package/src/commands/shell.js +164 -0
- package/src/commands/sites.js +9 -3
- package/src/commands/status.js +9 -3
- package/src/commands/update.js +6 -4
- package/src/index.js +43 -7
- package/src/lib/chisel.js +5 -5
- package/src/lib/config.js +2 -4
- package/src/lib/panel-api.js +298 -70
- package/src/lib/platform.js +1 -2
- package/src/lib/plist.js +3 -12
- package/src/lib/portlama-shell.sh +177 -0
- package/src/lib/scan.js +50 -14
- package/src/lib/ws-helpers.js +158 -0
package/LICENSE.md
CHANGED
|
@@ -13,6 +13,7 @@ You may use, copy, modify, and distribute this software for any **noncommercial*
|
|
|
13
13
|
Commercial use of this software requires a commercial license, available by contacting licence@codelama.com.tr.
|
|
14
14
|
|
|
15
15
|
Commercial use includes, but is not limited to:
|
|
16
|
+
|
|
16
17
|
- Using Portlama in a business to serve data to clients or partners
|
|
17
18
|
- Deploying Portlama as part of a revenue-generating service or product
|
|
18
19
|
- Using Portlama internally at a for-profit organization
|
|
@@ -85,7 +86,7 @@ The first time you are notified in writing that you have violated any of these t
|
|
|
85
86
|
|
|
86
87
|
### No Liability
|
|
87
88
|
|
|
88
|
-
|
|
89
|
+
**_As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim._**
|
|
89
90
|
|
|
90
91
|
### Definitions
|
|
91
92
|
|
package/README.md
CHANGED
|
@@ -15,17 +15,17 @@ connection details and an agent-scoped mTLS certificate.
|
|
|
15
15
|
|
|
16
16
|
## Commands
|
|
17
17
|
|
|
18
|
-
| Command
|
|
19
|
-
|
|
|
20
|
-
| `setup`
|
|
21
|
-
| `update`
|
|
22
|
-
| `uninstall`
|
|
23
|
-
| `status`
|
|
24
|
-
| `logs`
|
|
25
|
-
| `sites`
|
|
26
|
-
| `sites create <name>`
|
|
27
|
-
| `sites delete <name-or-id>`
|
|
28
|
-
| `deploy <name-or-id> <local-path>` | Deploy a local directory to a site
|
|
18
|
+
| Command | Description |
|
|
19
|
+
| ---------------------------------- | ------------------------------------------ |
|
|
20
|
+
| `setup` | Install Chisel and configure the tunnel |
|
|
21
|
+
| `update` | Update Chisel binary to latest version |
|
|
22
|
+
| `uninstall` | Remove Chisel, plist, and configuration |
|
|
23
|
+
| `status` | Show tunnel connection status |
|
|
24
|
+
| `logs` | Display recent tunnel logs |
|
|
25
|
+
| `sites` | List all static sites |
|
|
26
|
+
| `sites create <name>` | Create a new static site (admin cert only) |
|
|
27
|
+
| `sites delete <name-or-id>` | Delete a static site (admin cert only) |
|
|
28
|
+
| `deploy <name-or-id> <local-path>` | Deploy a local directory to a site |
|
|
29
29
|
|
|
30
30
|
### Sites Command
|
|
31
31
|
|
|
@@ -57,12 +57,12 @@ portlama-agent sites create docs --spa --auth
|
|
|
57
57
|
portlama-agent sites create myblog --type custom --domain myblog.com
|
|
58
58
|
```
|
|
59
59
|
|
|
60
|
-
| Flag
|
|
61
|
-
|
|
|
62
|
-
| `--type <managed\|custom>` | `managed` | Site type: managed subdomain or custom domain
|
|
63
|
-
| `--domain <fqdn>`
|
|
64
|
-
| `--spa`
|
|
65
|
-
| `--auth`
|
|
60
|
+
| Flag | Default | Description |
|
|
61
|
+
| -------------------------- | --------- | --------------------------------------------------- |
|
|
62
|
+
| `--type <managed\|custom>` | `managed` | Site type: managed subdomain or custom domain |
|
|
63
|
+
| `--domain <fqdn>` | — | Custom domain (required when `--type custom`) |
|
|
64
|
+
| `--spa` | off | Enable SPA mode (serve `index.html` for all routes) |
|
|
65
|
+
| `--auth` | off | Enable Authelia protection |
|
|
66
66
|
|
|
67
67
|
**Delete a site:**
|
|
68
68
|
|
|
@@ -102,10 +102,10 @@ portlama-agent deploy blog ./dist
|
|
|
102
102
|
|
|
103
103
|
## Requirements
|
|
104
104
|
|
|
105
|
-
| Requirement | Details
|
|
106
|
-
| ----------- |
|
|
107
|
-
| OS | macOS
|
|
108
|
-
| Node.js | >= 20.0.0
|
|
105
|
+
| Requirement | Details |
|
|
106
|
+
| ----------- | ------------------------------- |
|
|
107
|
+
| OS | macOS |
|
|
108
|
+
| Node.js | >= 20.0.0 |
|
|
109
109
|
| Access | User account (no root required) |
|
|
110
110
|
|
|
111
111
|
## How It Works
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lamalibre/portlama-agent",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.4",
|
|
4
4
|
"description": "Mac agent for Portlama — installs Chisel tunnel client and manages launchd agent",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
@@ -35,7 +35,8 @@
|
|
|
35
35
|
"dependencies": {
|
|
36
36
|
"chalk": "^5.3.0",
|
|
37
37
|
"execa": "^9.0.0",
|
|
38
|
-
"listr2": "^8.0.0"
|
|
38
|
+
"listr2": "^8.0.0",
|
|
39
|
+
"ws": "^8.18.0"
|
|
39
40
|
},
|
|
40
41
|
"engines": {
|
|
41
42
|
"node": ">=20.0.0"
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { existsSync } from 'node:fs';
|
|
2
|
+
import { stat } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { assertMacOS } from '../lib/platform.js';
|
|
6
|
+
import { requireAgentConfig } from '../lib/config.js';
|
|
7
|
+
import { downloadRemoteFile, uploadRemoteFile } from '../lib/panel-api.js';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Parse a cp argument to determine if it's a remote path (agent-label:/path)
|
|
11
|
+
* or a local path.
|
|
12
|
+
* @param {string} arg
|
|
13
|
+
* @returns {{ isRemote: boolean, agentLabel?: string, path: string }}
|
|
14
|
+
*/
|
|
15
|
+
function parseLocation(arg) {
|
|
16
|
+
// Match pattern: label:/path (label must be lowercase alphanumeric with hyphens, matching server validation)
|
|
17
|
+
const match = arg.match(/^([a-z0-9-]+):(.+)$/);
|
|
18
|
+
if (match) {
|
|
19
|
+
return { isRemote: true, agentLabel: match[1], path: match[2] };
|
|
20
|
+
}
|
|
21
|
+
return { isRemote: false, path: arg };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Run the file copy command.
|
|
26
|
+
* Supports download (remote → local) and upload (local → remote).
|
|
27
|
+
* @param {string[]} args
|
|
28
|
+
*/
|
|
29
|
+
export async function runCp(args) {
|
|
30
|
+
assertMacOS();
|
|
31
|
+
const config = await requireAgentConfig();
|
|
32
|
+
|
|
33
|
+
if (args.length < 2) {
|
|
34
|
+
printUsage();
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const source = parseLocation(args[0]);
|
|
39
|
+
const dest = parseLocation(args[1]);
|
|
40
|
+
|
|
41
|
+
// Validate: exactly one side must be remote
|
|
42
|
+
if (source.isRemote && dest.isRemote) {
|
|
43
|
+
console.error(
|
|
44
|
+
chalk.red('\n Cannot copy between two remote agents. One side must be a local path.\n'),
|
|
45
|
+
);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!source.isRemote && !dest.isRemote) {
|
|
50
|
+
console.error(
|
|
51
|
+
chalk.red(
|
|
52
|
+
'\n Both paths are local. Use the system cp command for local copies.\n' +
|
|
53
|
+
' For remote transfers, prefix with agent-label: (e.g. myagent:/path/to/file)\n',
|
|
54
|
+
),
|
|
55
|
+
);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (source.isRemote) {
|
|
60
|
+
await runDownload(config, source, dest);
|
|
61
|
+
} else {
|
|
62
|
+
await runUpload(config, source, dest);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Download a file from a remote agent.
|
|
68
|
+
* @param {object} config
|
|
69
|
+
* @param {{ agentLabel: string, path: string }} source
|
|
70
|
+
* @param {{ path: string }} dest
|
|
71
|
+
*/
|
|
72
|
+
async function runDownload(config, source, dest) {
|
|
73
|
+
const agentLabel = source.agentLabel;
|
|
74
|
+
const remotePath = source.path;
|
|
75
|
+
let localPath = path.resolve(dest.path);
|
|
76
|
+
|
|
77
|
+
// If dest is a directory, use the remote filename
|
|
78
|
+
try {
|
|
79
|
+
const destStat = await stat(localPath);
|
|
80
|
+
if (destStat.isDirectory()) {
|
|
81
|
+
const remoteBasename = path.basename(remotePath);
|
|
82
|
+
localPath = path.join(localPath, remoteBasename);
|
|
83
|
+
}
|
|
84
|
+
} catch {
|
|
85
|
+
// Path doesn't exist yet — that's fine, we'll create the file
|
|
86
|
+
// Ensure the parent directory exists
|
|
87
|
+
const parentDir = path.dirname(localPath);
|
|
88
|
+
if (!existsSync(parentDir)) {
|
|
89
|
+
console.error(chalk.red(`\n Parent directory does not exist: ${parentDir}\n`));
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
console.log('');
|
|
95
|
+
console.log(chalk.dim(` Downloading ${chalk.bold(agentLabel)}:${remotePath} → ${localPath}`));
|
|
96
|
+
|
|
97
|
+
try {
|
|
98
|
+
await downloadRemoteFile(
|
|
99
|
+
config.panelUrl,
|
|
100
|
+
config.p12Path,
|
|
101
|
+
config.p12Password,
|
|
102
|
+
agentLabel,
|
|
103
|
+
remotePath,
|
|
104
|
+
localPath,
|
|
105
|
+
);
|
|
106
|
+
} catch (err) {
|
|
107
|
+
console.error(chalk.red(`\n Download failed: ${err.message}\n`));
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
console.log(` ${chalk.green('✓')} Downloaded to ${chalk.cyan(localPath)}`);
|
|
112
|
+
console.log('');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Upload a local file to a remote agent.
|
|
117
|
+
* @param {object} config
|
|
118
|
+
* @param {{ path: string }} source
|
|
119
|
+
* @param {{ agentLabel: string, path: string }} dest
|
|
120
|
+
*/
|
|
121
|
+
async function runUpload(config, source, dest) {
|
|
122
|
+
const localPath = path.resolve(source.path);
|
|
123
|
+
const agentLabel = dest.agentLabel;
|
|
124
|
+
const remotePath = dest.path;
|
|
125
|
+
|
|
126
|
+
// Validate local file exists
|
|
127
|
+
if (!existsSync(localPath)) {
|
|
128
|
+
console.error(chalk.red(`\n Local file not found: ${localPath}\n`));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let localStat;
|
|
133
|
+
try {
|
|
134
|
+
localStat = await stat(localPath);
|
|
135
|
+
} catch (err) {
|
|
136
|
+
console.error(chalk.red(`\n Cannot read file: ${err.message}\n`));
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!localStat.isFile()) {
|
|
141
|
+
console.error(
|
|
142
|
+
chalk.red('\n Only single file transfers are currently supported.\n') +
|
|
143
|
+
chalk.dim(
|
|
144
|
+
' To transfer a directory, archive it first (e.g. tar -czf archive.tar.gz dir/).\n',
|
|
145
|
+
),
|
|
146
|
+
);
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
console.log('');
|
|
151
|
+
console.log(chalk.dim(` Uploading ${localPath} → ${chalk.bold(agentLabel)}:${remotePath}`));
|
|
152
|
+
|
|
153
|
+
try {
|
|
154
|
+
await uploadRemoteFile(
|
|
155
|
+
config.panelUrl,
|
|
156
|
+
config.p12Path,
|
|
157
|
+
config.p12Password,
|
|
158
|
+
agentLabel,
|
|
159
|
+
remotePath,
|
|
160
|
+
localPath,
|
|
161
|
+
);
|
|
162
|
+
} catch (err) {
|
|
163
|
+
console.error(chalk.red(`\n Upload failed: ${err.message}\n`));
|
|
164
|
+
process.exit(1);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
console.log(` ${chalk.green('✓')} Uploaded to ${chalk.cyan(`${agentLabel}:${remotePath}`)}`);
|
|
168
|
+
console.log('');
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Print usage information.
|
|
173
|
+
*/
|
|
174
|
+
function printUsage() {
|
|
175
|
+
const c = chalk.cyan;
|
|
176
|
+
const d = chalk.dim;
|
|
177
|
+
|
|
178
|
+
console.error(`
|
|
179
|
+
${chalk.bold('Usage:')}
|
|
180
|
+
|
|
181
|
+
${c('portlama-agent cp')} ${d('<source> <destination>')}
|
|
182
|
+
|
|
183
|
+
${chalk.bold('Download from agent:')}
|
|
184
|
+
|
|
185
|
+
${c('portlama-agent cp myagent:/var/log/app.log ./app.log')}
|
|
186
|
+
|
|
187
|
+
${chalk.bold('Upload to agent:')}
|
|
188
|
+
|
|
189
|
+
${c('portlama-agent cp ./config.json myagent:/etc/app/config.json')}
|
|
190
|
+
|
|
191
|
+
${chalk.bold('Notes:')}
|
|
192
|
+
|
|
193
|
+
Remote paths use the format: ${c('agent-label:/absolute/path')}
|
|
194
|
+
Only single file transfers are supported.
|
|
195
|
+
`);
|
|
196
|
+
}
|
package/src/commands/deploy.js
CHANGED
|
@@ -26,7 +26,7 @@ async function scanDirectory(dir, base = '') {
|
|
|
26
26
|
const fullPath = path.join(dir, entry.name);
|
|
27
27
|
const relPath = base ? path.join(base, entry.name) : entry.name;
|
|
28
28
|
if (entry.isDirectory()) {
|
|
29
|
-
results.push(...await scanDirectory(fullPath, relPath));
|
|
29
|
+
results.push(...(await scanDirectory(fullPath, relPath)));
|
|
30
30
|
} else if (entry.isFile()) {
|
|
31
31
|
const s = await stat(fullPath);
|
|
32
32
|
results.push({ relativePath: relPath, absolutePath: fullPath, size: s.size });
|
|
@@ -112,7 +112,9 @@ export async function runDeploy(args) {
|
|
|
112
112
|
const ext = path.extname(f.relativePath) || '(no extension)';
|
|
113
113
|
console.error(` ${chalk.yellow(ext)} ${f.relativePath}`);
|
|
114
114
|
}
|
|
115
|
-
console.error(
|
|
115
|
+
console.error(
|
|
116
|
+
`\n ${chalk.dim('Only static web assets are allowed. Remove these files and retry.')}\n`,
|
|
117
|
+
);
|
|
116
118
|
process.exit(1);
|
|
117
119
|
}
|
|
118
120
|
|
|
@@ -160,12 +162,20 @@ export async function runDeploy(args) {
|
|
|
160
162
|
title: 'Clearing remote files',
|
|
161
163
|
task: async (_ctx, task) => {
|
|
162
164
|
const { files: remoteFiles } = await fetchSiteFiles(
|
|
163
|
-
config.panelUrl,
|
|
165
|
+
config.panelUrl,
|
|
166
|
+
config.p12Path,
|
|
167
|
+
config.p12Password,
|
|
168
|
+
siteId,
|
|
169
|
+
'.',
|
|
164
170
|
);
|
|
165
171
|
for (const f of remoteFiles) {
|
|
166
172
|
task.output = `Removing ${f.name}`;
|
|
167
173
|
await deleteSiteFile(
|
|
168
|
-
config.panelUrl,
|
|
174
|
+
config.panelUrl,
|
|
175
|
+
config.p12Path,
|
|
176
|
+
config.p12Password,
|
|
177
|
+
siteId,
|
|
178
|
+
f.name,
|
|
169
179
|
);
|
|
170
180
|
}
|
|
171
181
|
},
|
|
@@ -190,8 +200,11 @@ export async function runDeploy(args) {
|
|
|
190
200
|
const batch = groupFiles.slice(i, i + 10);
|
|
191
201
|
const uploadDir = dir === '.' ? '.' : dir;
|
|
192
202
|
await uploadSiteFiles(
|
|
193
|
-
config.panelUrl,
|
|
194
|
-
|
|
203
|
+
config.panelUrl,
|
|
204
|
+
config.p12Path,
|
|
205
|
+
config.p12Password,
|
|
206
|
+
siteId,
|
|
207
|
+
uploadDir,
|
|
195
208
|
batch.map((f) => f.absolutePath),
|
|
196
209
|
);
|
|
197
210
|
uploaded += batch.length;
|
|
@@ -208,7 +221,11 @@ export async function runDeploy(args) {
|
|
|
208
221
|
let remoteCount = 0;
|
|
209
222
|
const countRemote = async (dirPath) => {
|
|
210
223
|
const { files: entries } = await fetchSiteFiles(
|
|
211
|
-
config.panelUrl,
|
|
224
|
+
config.panelUrl,
|
|
225
|
+
config.p12Path,
|
|
226
|
+
config.p12Password,
|
|
227
|
+
siteId,
|
|
228
|
+
dirPath,
|
|
212
229
|
);
|
|
213
230
|
for (const entry of entries) {
|
|
214
231
|
if (entry.type === 'directory') {
|
|
@@ -223,7 +240,9 @@ export async function runDeploy(args) {
|
|
|
223
240
|
const localCount = files.length;
|
|
224
241
|
if (remoteCount !== localCount) {
|
|
225
242
|
task.output = `Warning: remote has ${remoteCount} files but ${localCount} were uploaded`;
|
|
226
|
-
throw new Error(
|
|
243
|
+
throw new Error(
|
|
244
|
+
`Verification failed: expected ${localCount} files on remote but found ${remoteCount}`,
|
|
245
|
+
);
|
|
227
246
|
}
|
|
228
247
|
task.output = `${remoteCount} files verified`;
|
|
229
248
|
},
|
|
@@ -246,6 +265,8 @@ export async function runDeploy(args) {
|
|
|
246
265
|
// Print summary
|
|
247
266
|
const totalSize = files.reduce((sum, f) => sum + f.size, 0);
|
|
248
267
|
console.log('');
|
|
249
|
-
console.log(
|
|
268
|
+
console.log(
|
|
269
|
+
` ${chalk.green('✓')} Deployed ${chalk.bold(String(files.length))} files (${formatBytes(totalSize)}) to ${chalk.cyan(`https://${siteFqdn}/`)}`,
|
|
270
|
+
);
|
|
250
271
|
console.log('');
|
|
251
272
|
}
|
package/src/commands/setup.js
CHANGED
|
@@ -7,6 +7,7 @@ import chalk from 'chalk';
|
|
|
7
7
|
import { assertMacOS, CHISEL_BIN_DIR, LOGS_DIR } from '../lib/platform.js';
|
|
8
8
|
import { loadAgentConfig, saveAgentConfig } from '../lib/config.js';
|
|
9
9
|
import { fetchHealth, fetchPlist, fetchTunnels } from '../lib/panel-api.js';
|
|
10
|
+
import { extractPemFromP12, cleanupPemFiles } from '../lib/ws-helpers.js';
|
|
10
11
|
import { installChisel } from '../lib/chisel.js';
|
|
11
12
|
import { rewritePlist, writePlistFile } from '../lib/plist.js';
|
|
12
13
|
import { isAgentLoaded, unloadAgent, loadAgent, getAgentPid } from '../lib/launchctl.js';
|
|
@@ -58,10 +59,7 @@ export async function runSetup() {
|
|
|
58
59
|
console.log(chalk.dim(' Panel → Certificates → Agent Certificates → Generate'));
|
|
59
60
|
console.log('');
|
|
60
61
|
|
|
61
|
-
const panelUrl = await prompt(
|
|
62
|
-
'Panel URL (e.g. https://1.2.3.4:9292)',
|
|
63
|
-
existingConfig?.panelUrl,
|
|
64
|
-
);
|
|
62
|
+
const panelUrl = await prompt('Panel URL (e.g. https://1.2.3.4:9292)', existingConfig?.panelUrl);
|
|
65
63
|
if (!panelUrl) {
|
|
66
64
|
throw new Error('Panel URL is required.');
|
|
67
65
|
}
|
|
@@ -98,19 +96,33 @@ export async function runSetup() {
|
|
|
98
96
|
const tasks = new Listr(
|
|
99
97
|
[
|
|
100
98
|
{
|
|
101
|
-
title: '
|
|
99
|
+
title: 'Creating directories',
|
|
100
|
+
task: async () => {
|
|
101
|
+
await mkdir(CHISEL_BIN_DIR, { recursive: true });
|
|
102
|
+
await mkdir(LOGS_DIR, { recursive: true });
|
|
103
|
+
},
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
title: 'Extracting certificates from P12',
|
|
102
107
|
task: async (_ctx, task) => {
|
|
103
|
-
const
|
|
104
|
-
|
|
108
|
+
const pem = await extractPemFromP12(ctx.p12Path, ctx.p12Password);
|
|
109
|
+
if (pem.caPath) {
|
|
110
|
+
task.output = `mTLS CA certificate saved to ${pem.caPath}`;
|
|
111
|
+
} else {
|
|
112
|
+
task.output = 'No CA certificate found in P12';
|
|
113
|
+
}
|
|
114
|
+
// Clean up temporary PEM cert/key files — they are only needed transiently
|
|
115
|
+
await cleanupPemFiles(pem);
|
|
105
116
|
},
|
|
106
117
|
rendererOptions: { persistentOutput: true },
|
|
107
118
|
},
|
|
108
119
|
{
|
|
109
|
-
title: '
|
|
110
|
-
task: async () => {
|
|
111
|
-
await
|
|
112
|
-
|
|
120
|
+
title: 'Verifying panel connectivity',
|
|
121
|
+
task: async (_ctx, task) => {
|
|
122
|
+
const health = await fetchHealth(ctx.panelUrl, ctx.p12Path, ctx.p12Password);
|
|
123
|
+
task.output = `Panel is reachable (status: ${health.status || 'ok'})`;
|
|
113
124
|
},
|
|
125
|
+
rendererOptions: { persistentOutput: true },
|
|
114
126
|
},
|
|
115
127
|
{
|
|
116
128
|
title: 'Installing Chisel',
|
|
@@ -179,9 +191,7 @@ export async function runSetup() {
|
|
|
179
191
|
if (loaded) {
|
|
180
192
|
task.output = 'Agent loaded (process starting...)';
|
|
181
193
|
} else {
|
|
182
|
-
throw new Error(
|
|
183
|
-
'Agent failed to load. Check logs with: portlama-agent logs',
|
|
184
|
-
);
|
|
194
|
+
throw new Error('Agent failed to load. Check logs with: portlama-agent logs');
|
|
185
195
|
}
|
|
186
196
|
}
|
|
187
197
|
},
|
|
@@ -230,30 +240,66 @@ function printSetupSummary(ctx) {
|
|
|
230
240
|
|
|
231
241
|
console.log('');
|
|
232
242
|
console.log(c(' ╔══════════════════════════════════════════════════════════╗'));
|
|
233
|
-
console.log(
|
|
243
|
+
console.log(
|
|
244
|
+
c(' ║') + ` ${g.bold('Portlama Agent installed successfully!')}` + ' '.repeat(17) + c('║'),
|
|
245
|
+
);
|
|
234
246
|
console.log(c(' ╠══════════════════════════════════════════════════════════╣'));
|
|
235
247
|
|
|
236
248
|
if (ctx.domain) {
|
|
237
|
-
console.log(
|
|
249
|
+
console.log(
|
|
250
|
+
c(' ║') +
|
|
251
|
+
` ${b('Domain:')} ${c(ctx.domain)}` +
|
|
252
|
+
' '.repeat(Math.max(0, 46 - ctx.domain.length)) +
|
|
253
|
+
c('║'),
|
|
254
|
+
);
|
|
238
255
|
}
|
|
239
256
|
|
|
240
|
-
console.log(
|
|
241
|
-
|
|
257
|
+
console.log(
|
|
258
|
+
c(' ║') +
|
|
259
|
+
` ${b('Chisel:')} ${ctx.chiselVersion}` +
|
|
260
|
+
' '.repeat(Math.max(0, 46 - (ctx.chiselVersion || '').length)) +
|
|
261
|
+
c('║'),
|
|
262
|
+
);
|
|
263
|
+
console.log(
|
|
264
|
+
c(' ║') + ` ${b('Tunnels:')} ${ctx.tunnels.length} configured` + ' '.repeat(33) + c('║'),
|
|
265
|
+
);
|
|
242
266
|
console.log(c(' ║') + ' '.repeat(58) + c('║'));
|
|
243
267
|
|
|
244
268
|
if (ctx.tunnels.length > 0) {
|
|
245
269
|
for (const t of ctx.tunnels) {
|
|
246
270
|
const line = `${t.subdomain} → localhost:${t.port}`;
|
|
247
|
-
console.log(
|
|
271
|
+
console.log(
|
|
272
|
+
c(' ║') + ` ${d('•')} ${line}` + ' '.repeat(Math.max(0, 54 - line.length)) + c('║'),
|
|
273
|
+
);
|
|
248
274
|
}
|
|
249
275
|
console.log(c(' ║') + ' '.repeat(58) + c('║'));
|
|
250
276
|
}
|
|
251
277
|
|
|
252
278
|
console.log(c(' ║') + ` ${b('Commands:')}` + ' '.repeat(47) + c('║'));
|
|
253
|
-
console.log(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
279
|
+
console.log(
|
|
280
|
+
c(' ║') +
|
|
281
|
+
` ${d('portlama-agent status')} ${d('— check agent health')}` +
|
|
282
|
+
' '.repeat(11) +
|
|
283
|
+
c('║'),
|
|
284
|
+
);
|
|
285
|
+
console.log(
|
|
286
|
+
c(' ║') +
|
|
287
|
+
` ${d('portlama-agent logs')} ${d('— stream chisel logs')}` +
|
|
288
|
+
' '.repeat(11) +
|
|
289
|
+
c('║'),
|
|
290
|
+
);
|
|
291
|
+
console.log(
|
|
292
|
+
c(' ║') +
|
|
293
|
+
` ${d('portlama-agent update')} ${d('— refresh tunnel config')}` +
|
|
294
|
+
' '.repeat(8) +
|
|
295
|
+
c('║'),
|
|
296
|
+
);
|
|
297
|
+
console.log(
|
|
298
|
+
c(' ║') +
|
|
299
|
+
` ${d('portlama-agent uninstall')} ${d('— remove everything')}` +
|
|
300
|
+
' '.repeat(12) +
|
|
301
|
+
c('║'),
|
|
302
|
+
);
|
|
257
303
|
console.log(c(' ║') + ' '.repeat(58) + c('║'));
|
|
258
304
|
console.log(c(' ╚══════════════════════════════════════════════════════════╝'));
|
|
259
305
|
console.log('');
|