bdy 1.23.16-dev-target-commands → 1.23.16-dev
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/distTs/README.md +181 -0
- package/distTs/package.json +3 -3
- package/distTs/src/agent/agent.js +27 -2
- package/distTs/src/agent/linux.js +7 -5
- package/distTs/src/agent/manager.js +25 -0
- package/distTs/src/agent/osx.js +7 -9
- package/distTs/src/agent/socket/client.js +10 -0
- package/distTs/src/agent/system.js +16 -4
- package/distTs/src/agent/windows.js +1 -2
- package/distTs/src/api/client.js +5 -2
- package/distTs/src/command/agent/install.js +1 -1
- package/distTs/src/command/agent/run.js +16 -0
- package/distTs/src/command/project/get.js +18 -0
- package/distTs/src/command/project/set.js +31 -0
- package/distTs/src/command/sandbox/app/list.js +6 -1
- package/distTs/src/command/sandbox/create.js +2 -2
- package/distTs/src/command/sandbox/get/yaml.js +30 -0
- package/distTs/src/command/vt/scrape.js +193 -0
- package/distTs/src/input.js +16 -1
- package/distTs/src/logger.js +7 -1
- package/distTs/src/texts.js +28 -22
- package/distTs/src/tunnel/server/sftp.js +24 -5
- package/distTs/src/tunnel/ssh/client.js +35 -4
- package/distTs/src/types/tunnel.js +11 -0
- package/distTs/src/utils.js +2 -2
- package/package.json +3 -3
- package/distTs/detect-rules.json +0 -351
- package/distTs/src/command/pipeline/run/apply.js +0 -62
- package/distTs/src/command/yaml/actions/detect.js +0 -268
- package/distTs/src/command/yaml/actions/info.js +0 -56
- package/distTs/src/command/yaml/actions/list.js +0 -70
- package/distTs/src/command/yaml/actions/schema.js +0 -104
- package/distTs/src/command/yaml/actions.js +0 -13
- package/distTs/src/command/yaml/agents.js +0 -88
- package/distTs/src/command/yaml/cache.js +0 -98
- package/distTs/src/command/yaml/init.js +0 -110
- package/distTs/src/command/yaml/pipeline.js +0 -42
- package/distTs/src/command/yaml/render.js +0 -83
- package/distTs/src/command/yaml/schemaUtils.js +0 -139
- package/distTs/src/command/yaml/validate.js +0 -259
- package/distTs/src/command/yaml.js +0 -16
- package/distTs/src/diskCache.js +0 -82
package/distTs/README.md
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# Buddy CLI (`bdy`)
|
|
2
|
+
|
|
3
|
+
Work seamlessly with [Buddy](https://buddy.works) from the command line.
|
|
4
|
+
|
|
5
|
+
`bdy` is Buddy's official command-line interface for managing CI/CD pipelines, agents, tests, artifacts, sandboxes, distributions, and more — straight from your terminal.
|
|
6
|
+
|
|
7
|
+
📚 Full documentation: https://buddy.works/docs/cli/getting-started
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
### npm (recommended)
|
|
12
|
+
|
|
13
|
+
Requires Node.js 14+.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
# macOS / Linux
|
|
17
|
+
sudo npm i -g bdy
|
|
18
|
+
|
|
19
|
+
# Windows
|
|
20
|
+
npm i -g bdy
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Homebrew (macOS)
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
brew tap buddy/bdy
|
|
27
|
+
brew install bdy
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### APT (Linux x64 / ARM64)
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
sudo apt-get update && sudo apt-get install -y software-properties-common
|
|
34
|
+
sudo gpg --homedir /tmp --no-default-keyring --keyring /usr/share/keyrings/buddy.gpg --keyserver hkp://keyserver.ubuntu.com:80 --recv-keys eb39332e766364ca6220e8dc631c5a16310cc0ad
|
|
35
|
+
echo "deb [arch=amd64 signed-by=/usr/share/keyrings/buddy.gpg] https://es.buddy.works/bdy/apt-repo prod main" | sudo tee /etc/apt/sources.list.d/buddy.list > /dev/null
|
|
36
|
+
sudo apt-get update && sudo apt-get install -y bdy
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
> On ARM64, replace `arch=amd64` with `arch=arm64` in the `echo` command above.
|
|
40
|
+
|
|
41
|
+
### Chocolatey (Windows)
|
|
42
|
+
|
|
43
|
+
```powershell
|
|
44
|
+
choco install bdy --pre
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Direct download
|
|
48
|
+
|
|
49
|
+
Prebuilt binaries are available for macOS (Apple Silicon), Linux (x64/ARM64), and Windows (x64) on the [installation page](https://buddy.works/docs/cli/getting-started).
|
|
50
|
+
|
|
51
|
+
### Verify & update
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
bdy version # show current and latest version
|
|
55
|
+
sudo npm i -g bdy # update (or use your package manager's upgrade command)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Authentication
|
|
59
|
+
|
|
60
|
+
A [Buddy account](https://buddy.works) is required to use the CLI.
|
|
61
|
+
|
|
62
|
+
Log in interactively:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
bdy login
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
New to Buddy? Create an account from the CLI:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
bdy register
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Linking a project
|
|
75
|
+
|
|
76
|
+
Once logged in, link a directory to a Buddy project:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
mkdir my-proj
|
|
80
|
+
cd my-proj
|
|
81
|
+
bdy proj link
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
After linking, every `bdy` command run from this directory is executed against the linked project — no need to pass the project name each time.
|
|
85
|
+
|
|
86
|
+
Check who you're logged in as, or log out:
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
bdy whoami
|
|
90
|
+
bdy logout
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### Tokens & non-interactive login
|
|
94
|
+
|
|
95
|
+
For CI/CD pipelines and non-browser environments, authenticate with a [personal access token](https://buddy.works/docs/api/getting-started/oauth2/personal-access-token):
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
bdy login --token <token>
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The login command accepts the following options:
|
|
102
|
+
|
|
103
|
+
| Option | Description | Environment variable |
|
|
104
|
+
| --- | --- | --- |
|
|
105
|
+
| `--token <token>` | Personal access token | `BUDDY_TOKEN` |
|
|
106
|
+
| `--api <url>` | API URL for On-Premises installations | `BUDDY_API_ENDPOINT` |
|
|
107
|
+
| `--region <region>` | Region: `us`, `eu`, or `as` | `BUDDY_REGION` |
|
|
108
|
+
| `-w, --workspace <domain>` | Workspace URL handle | `BUDDY_WORKSPACE` |
|
|
109
|
+
|
|
110
|
+
Each option can be supplied as a flag or via its environment variable — handy for pipelines and headless setups:
|
|
111
|
+
|
|
112
|
+
```bash
|
|
113
|
+
export BUDDY_TOKEN=<token>
|
|
114
|
+
export BUDDY_WORKSPACE=<your-workspace>
|
|
115
|
+
bdy login
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Agent integration (plugin & skills)
|
|
119
|
+
|
|
120
|
+
The [Buddy Plugin](https://github.com/buddy/buddy-plugin) lets coding agents deploy applications, expose local services, and manage infrastructure on Buddy. It bundles a consolidated skill covering sandboxes, artifacts, tunnels, domains, distributions, and pipelines, plus two commands:
|
|
121
|
+
|
|
122
|
+
- `/deploy [name] [path]` — deploy a static site or server application
|
|
123
|
+
- `/expose [port]` — open a tunnel to a locally running service
|
|
124
|
+
|
|
125
|
+
Make sure the CLI is installed first (`sudo npm install -g bdy`), then:
|
|
126
|
+
|
|
127
|
+
**Claude Code** — install the plugin (commands + skills):
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
claude plugin marketplace add buddy/buddy-plugin
|
|
131
|
+
claude plugin install buddy@buddy-plugin
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**Other agents** — install the skills only:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
npx skills add buddy/buddy-plugin
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Then authenticate and link your project:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
bdy login
|
|
144
|
+
cd your-project
|
|
145
|
+
bdy proj link
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The plugin auto-detects your project type: static sites generate versioned artifacts with public URLs, while server applications deploy to sandboxes with HTTPS endpoints. Monorepos can deploy multiple applications, each getting its own URL and endpoint.
|
|
149
|
+
|
|
150
|
+
## Commands
|
|
151
|
+
|
|
152
|
+
| Command | Description |
|
|
153
|
+
| --- | --- |
|
|
154
|
+
| `bdy login` / `sign-in` | Log in to Buddy |
|
|
155
|
+
| `bdy register` / `sign-up` | Register a new Buddy account |
|
|
156
|
+
| `bdy whoami` | Check login information |
|
|
157
|
+
| `bdy logout` | Log out from Buddy |
|
|
158
|
+
| `bdy workspace` / `ws` | Manage workspaces |
|
|
159
|
+
| `bdy project` / `proj` | Manage projects |
|
|
160
|
+
| `bdy pipeline` / `pip` | Interact with the pipeline service |
|
|
161
|
+
| `bdy artifact` / `art` | Interact with the artifact service |
|
|
162
|
+
| `bdy sandbox` / `sb` | Interact with sandboxes |
|
|
163
|
+
| `bdy agent` | Install and run `bdy` as an OS service (Windows, macOS, Linux) |
|
|
164
|
+
| `bdy tunnel` | Manage tunnel configuration |
|
|
165
|
+
| `bdy crawl` | Manage web crawls (Markdown, HTML, PNG/JPEG screenshots; suite linking) |
|
|
166
|
+
| `bdy tests` | Manage unit tests and visual regression (Storybook, URL captures, runners) |
|
|
167
|
+
| `bdy domain` | Manage zones used in distribution routes |
|
|
168
|
+
| `bdy distro` | Manage distributions |
|
|
169
|
+
| `bdy api` | Contact the Buddy API directly |
|
|
170
|
+
| `bdy update` / `version` | Show version and update the CLI |
|
|
171
|
+
|
|
172
|
+
Run any command with `-h` / `--help` for detailed usage:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
bdy --help
|
|
176
|
+
bdy pipeline --help
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Feedback
|
|
180
|
+
|
|
181
|
+
Found a bug or have a feature request? Let us know through [Buddy support](https://buddy.works/support) or the [documentation](https://buddy.works/docs/cli/getting-started).
|
package/distTs/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bdy",
|
|
3
3
|
"preferGlobal": false,
|
|
4
|
-
"version": "1.23.16-dev
|
|
4
|
+
"version": "1.23.16-dev",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"homepage": "https://buddy.works/docs/cli",
|
|
@@ -53,7 +53,7 @@
|
|
|
53
53
|
"@fastify/cors": "11.2.0",
|
|
54
54
|
"@inquirer/prompts": "8.5.2",
|
|
55
55
|
"@modelcontextprotocol/sdk": "1.30.0",
|
|
56
|
-
"@puppeteer/browsers": "2.
|
|
56
|
+
"@puppeteer/browsers": "3.2.0",
|
|
57
57
|
"@scalar/json-magic": "0.12.16",
|
|
58
58
|
"@scalar/openapi-parser": "0.28.7",
|
|
59
59
|
"@scalar/openapi-types": "0.9.1",
|
|
@@ -81,7 +81,7 @@
|
|
|
81
81
|
"picomatch": "4.0.4",
|
|
82
82
|
"pino": "10.3.1",
|
|
83
83
|
"punycode": "2.3.1",
|
|
84
|
-
"puppeteer-core": "
|
|
84
|
+
"puppeteer-core": "25.6.0",
|
|
85
85
|
"range-parser": "1.2.1",
|
|
86
86
|
"socket.io-client": "4.8.3",
|
|
87
87
|
"ssh2": "1.17.0",
|
|
@@ -343,12 +343,16 @@ class Agent extends events_1.default {
|
|
|
343
343
|
this.socket.fetch(!this.disabled, null, null, false, this.getTunnelsUpdate(), true);
|
|
344
344
|
}
|
|
345
345
|
}
|
|
346
|
-
// Matches on the
|
|
347
|
-
//
|
|
346
|
+
// Matches on the request values exactly, which is what lets an update status share the
|
|
347
|
+
// field: TUNNEL_AGENT_UPDATE_STATUS values fall through here and do nothing, so an
|
|
348
348
|
// outcome we reported ourselves is never read back as a fresh request. Anything unknown
|
|
349
349
|
// is left alone rather than acked, so a value a newer front understands is still there
|
|
350
350
|
// when the agent is upgraded.
|
|
351
351
|
async makeAction(action) {
|
|
352
|
+
if (action === tunnel_1.TUNNEL_AGENT_ACTION.DEBUG_ON ||
|
|
353
|
+
action === tunnel_1.TUNNEL_AGENT_ACTION.DEBUG_OFF) {
|
|
354
|
+
return this.makeDebugAction(action === tunnel_1.TUNNEL_AGENT_ACTION.DEBUG_ON);
|
|
355
|
+
}
|
|
352
356
|
if (action === tunnel_1.TUNNEL_AGENT_ACTION.DELETE) {
|
|
353
357
|
if (this.manager) {
|
|
354
358
|
return this.manager.disableAgentAndExit(texts_1.ERR_AGENT_REMOVED);
|
|
@@ -373,6 +377,27 @@ class Agent extends events_1.default {
|
|
|
373
377
|
return;
|
|
374
378
|
}
|
|
375
379
|
}
|
|
380
|
+
// Applied from here rather than through the local api that `agent debug` calls: that api
|
|
381
|
+
// exists so another process can reach a running agent, and this is already inside one.
|
|
382
|
+
// A foreground tunnel has no manager and no config to record the level in, so it only
|
|
383
|
+
// changes its own - which is all it has to change.
|
|
384
|
+
//
|
|
385
|
+
// Always acked, and idempotent on purpose. The backend rewrites the whole agent row per
|
|
386
|
+
// request, so an ack can be undone by a concurrent update and the request comes back on
|
|
387
|
+
// the next fetch (see reclearLostUpdateStatus) - re-applying a level the process already
|
|
388
|
+
// runs at costs nothing, while rewriting the config every 15 seconds would, which is why
|
|
389
|
+
// the state check guards the write and not the ack.
|
|
390
|
+
makeDebugAction(debug) {
|
|
391
|
+
if (logger_1.default.debugOn !== debug) {
|
|
392
|
+
logger_1.default.info((0, texts_1.LOG_AGENT_DEBUG_REMOTE)(debug));
|
|
393
|
+
if (!this.manager)
|
|
394
|
+
logger_1.default.setDebug(debug);
|
|
395
|
+
else if (!this.manager.setDebug(debug)) {
|
|
396
|
+
logger_1.default.info(texts_1.LOG_AGENT_DEBUG_NOT_PERSISTED);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
return this.ackAction();
|
|
400
|
+
}
|
|
376
401
|
refreshTunnels(tunnels) {
|
|
377
402
|
if (this.tunnelsUpdating)
|
|
378
403
|
return;
|
|
@@ -49,14 +49,13 @@ class AgentLinux extends system_1.default {
|
|
|
49
49
|
// `systemctl show bdy` would hand the token to the same unprivileged users - and file modes
|
|
50
50
|
// cannot stop that. Closing this means the agent reading the token from a file only it can
|
|
51
51
|
// read, which is a change of where the token lives, not of this template.
|
|
52
|
-
getServiceConfig(id, host, token, port, user
|
|
52
|
+
getServiceConfig(id, host, token, port, user) {
|
|
53
53
|
const cli = this.getSystemBinaryPath();
|
|
54
54
|
return `[Unit]
|
|
55
55
|
Description=Buddy Tunnel Agent
|
|
56
56
|
After=network.target
|
|
57
57
|
|
|
58
58
|
[Service]
|
|
59
|
-
Environment="DEBUG=${debug ? 1 : 0}"
|
|
60
59
|
User=${user || 'root'}
|
|
61
60
|
WorkingDirectory=/
|
|
62
61
|
ExecStart=${cli} agent run --id="${id}" --host="${host}" --token="${token}" --port=${port}
|
|
@@ -84,7 +83,10 @@ WantedBy=multi-user.target`;
|
|
|
84
83
|
const user = m[1].trim();
|
|
85
84
|
return user === 'root' ? '' : user;
|
|
86
85
|
}
|
|
87
|
-
async install(id, host, token, port, start, user,
|
|
86
|
+
async install(id, host, token, port, start, user,
|
|
87
|
+
// Windows only - a unit runs as a user without being handed their password
|
|
88
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
89
|
+
_pass) {
|
|
88
90
|
try {
|
|
89
91
|
// ensure service clear
|
|
90
92
|
await this.uninstall();
|
|
@@ -105,7 +107,7 @@ WantedBy=multi-user.target`;
|
|
|
105
107
|
await this.extractBinaryArchive();
|
|
106
108
|
await this.fixBinaryRights();
|
|
107
109
|
logger_1.default.info(texts_1.LOG_AGENT_SYSTEM_SERVICE_CONFIG);
|
|
108
|
-
await this.saveFile(this.getServicePath(), this.getServiceConfig(id, host, token, port, user
|
|
110
|
+
await this.saveFile(this.getServicePath(), this.getServiceConfig(id, host, token, port, user));
|
|
109
111
|
// Everything above was written by this root process, so the service user does not own
|
|
110
112
|
// any of it yet and could not write its host key or its log. Ownership first, mode
|
|
111
113
|
// second - see chownSystemDir for why that order is the one that fails safely.
|
|
@@ -135,7 +137,7 @@ WantedBy=multi-user.target`;
|
|
|
135
137
|
catch {
|
|
136
138
|
// do nothing
|
|
137
139
|
}
|
|
138
|
-
await this.saveFile(this.getServicePath(), this.getServiceConfig(json.id, json.host, json.token, json.port, user
|
|
140
|
+
await this.saveFile(this.getServicePath(), this.getServiceConfig(json.id, json.host, json.token, json.port, user));
|
|
139
141
|
// The directory follows the service to its new user, and back to root when the user is
|
|
140
142
|
// cleared - this is what makes changing the user later work without 0777 everywhere
|
|
141
143
|
await this.chownSystemDir(user);
|
|
@@ -502,6 +502,28 @@ class AgentManagerClass {
|
|
|
502
502
|
return;
|
|
503
503
|
}
|
|
504
504
|
}
|
|
505
|
+
// The live level, and the record of it for the next start. Both entry points into an
|
|
506
|
+
// agent process read that record back - `agent run` for a service, startStandaloneProc
|
|
507
|
+
// for an `--app` install - so this write is what makes a toggle survive a restart.
|
|
508
|
+
//
|
|
509
|
+
// Best effort on the write, and the level is changed either way. The config normally
|
|
510
|
+
// belongs to the service user (chownSystemDir hands it over at 0644), so this succeeds
|
|
511
|
+
// for a service running as someone other than root - but a directory left behind by an
|
|
512
|
+
// older install, or a read-only filesystem, still ends up here, and refusing to raise
|
|
513
|
+
// the level over a file that only decides the *next* start would take remote debugging
|
|
514
|
+
// away from the machine in front of us. saveSystemConfig logs what went wrong, and
|
|
515
|
+
// makeDebugAction says what it cost.
|
|
516
|
+
setDebug(debug) {
|
|
517
|
+
logger_1.default.setDebug(debug);
|
|
518
|
+
let json;
|
|
519
|
+
try {
|
|
520
|
+
json = this.system.loadSystemConfig();
|
|
521
|
+
}
|
|
522
|
+
catch {
|
|
523
|
+
return false;
|
|
524
|
+
}
|
|
525
|
+
return this.system.saveSystemConfig(json.id, json.host, json.token, json.port, !!json.standalone, debug);
|
|
526
|
+
}
|
|
505
527
|
async processAgentDebug(req, res) {
|
|
506
528
|
if (!this.agent) {
|
|
507
529
|
this.serverError(res, 'Agent not enabled');
|
|
@@ -515,6 +537,9 @@ class AgentManagerClass {
|
|
|
515
537
|
this.serverError(res, 'Wrong input data');
|
|
516
538
|
return;
|
|
517
539
|
}
|
|
540
|
+
// The level only. The `agent debug` command on the other end of this writes the config
|
|
541
|
+
// itself, from a process that has checked it has the rights to - a second write of the
|
|
542
|
+
// same values from here would say nothing the first one does not.
|
|
518
543
|
logger_1.default.setDebug(!!data.debug);
|
|
519
544
|
logger_1.default.info(`MANAGER REQUEST: change debug to ${!!data.debug}`);
|
|
520
545
|
this.serverOutput(res, {
|
package/distTs/src/agent/osx.js
CHANGED
|
@@ -41,7 +41,7 @@ class AgentOsx extends system_1.default {
|
|
|
41
41
|
// this plist, and the same move on systemd leaks the token through `systemctl show` whatever
|
|
42
42
|
// the file mode is - see the note in AgentLinux.getServiceConfig. Closing this means the agent
|
|
43
43
|
// reading the token from a file only it can read, which is a change of where the token lives.
|
|
44
|
-
getServiceConfig(id, host, token, port, user
|
|
44
|
+
getServiceConfig(id, host, token, port, user) {
|
|
45
45
|
const cli = this.getSystemBinaryPath();
|
|
46
46
|
let cfg = `<?xml version='1.0' encoding='UTF-8'?>
|
|
47
47
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
@@ -63,11 +63,6 @@ class AgentOsx extends system_1.default {
|
|
|
63
63
|
<string>--port</string>
|
|
64
64
|
<string>${port}</string>
|
|
65
65
|
</array>
|
|
66
|
-
<key>EnvironmentVariables</key>
|
|
67
|
-
<dict>
|
|
68
|
-
<key>DEBUG</key>
|
|
69
|
-
<string>${debug ? 1 : 0}</string>
|
|
70
|
-
</dict>
|
|
71
66
|
<key>KeepAlive</key>
|
|
72
67
|
<true/>
|
|
73
68
|
<key>RunAtLoad</key>
|
|
@@ -99,7 +94,10 @@ class AgentOsx extends system_1.default {
|
|
|
99
94
|
const m = cfg.match(/<key>UserName<\/key>\s*<string>([^<]+)<\/string>/);
|
|
100
95
|
return m ? m[1].trim() : '';
|
|
101
96
|
}
|
|
102
|
-
async install(id, host, token, port, start, user,
|
|
97
|
+
async install(id, host, token, port, start, user,
|
|
98
|
+
// Windows only - a launchd job runs as a user without being handed their password
|
|
99
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
100
|
+
_pass) {
|
|
103
101
|
try {
|
|
104
102
|
// ensure service clear
|
|
105
103
|
await this.uninstall();
|
|
@@ -120,7 +118,7 @@ class AgentOsx extends system_1.default {
|
|
|
120
118
|
await this.extractBinaryArchive();
|
|
121
119
|
await this.fixBinaryRights();
|
|
122
120
|
logger_1.default.info(texts_1.LOG_AGENT_SYSTEM_SERVICE_CONFIG);
|
|
123
|
-
await this.saveFile(this.getServicePath(), this.getServiceConfig(id, host, token, port, user
|
|
121
|
+
await this.saveFile(this.getServicePath(), this.getServiceConfig(id, host, token, port, user));
|
|
124
122
|
// Everything above was written by this root process, so the service user does not own
|
|
125
123
|
// any of it yet and could not write its host key or its log. Ownership first, mode
|
|
126
124
|
// second - see chownSystemDir for why that order is the one that fails safely.
|
|
@@ -150,7 +148,7 @@ class AgentOsx extends system_1.default {
|
|
|
150
148
|
catch {
|
|
151
149
|
// do nothing
|
|
152
150
|
}
|
|
153
|
-
await this.saveFile(this.getServicePath(), this.getServiceConfig(json.id, json.host, json.token, json.port, user
|
|
151
|
+
await this.saveFile(this.getServicePath(), this.getServiceConfig(json.id, json.host, json.token, json.port, user));
|
|
154
152
|
// The directory follows the service to its new user, and back to root when the user is
|
|
155
153
|
// cleared - this is what makes changing the user later work without 0777 everywhere
|
|
156
154
|
await this.chownSystemDir(user);
|
|
@@ -55,6 +55,15 @@ class AgentSocketClient extends events_1.default {
|
|
|
55
55
|
version: this.version,
|
|
56
56
|
activate,
|
|
57
57
|
action: pending !== null ? pending : action,
|
|
58
|
+
// The live level, not the one the config records: DEBUG_ON is answered by this
|
|
59
|
+
// process, so what it runs at is the only answer worth reporting. Nothing reads it
|
|
60
|
+
// yet - the backend takes the fetch apart field by field and has nowhere to put
|
|
61
|
+
// this one, so the front offers both directions instead of a toggle. Sent anyway,
|
|
62
|
+
// and on every emit rather than only after a toggle: the day there is a column for
|
|
63
|
+
// it, every agent already in the field fills it in without being upgraded, and the
|
|
64
|
+
// whole row is rewritten per request so a payload that left it out would be a
|
|
65
|
+
// refresh tick clearing what a toggle had just reported.
|
|
66
|
+
debug: logger_1.default.debugOn,
|
|
58
67
|
disabled,
|
|
59
68
|
resetFirstHeard,
|
|
60
69
|
tunnels,
|
|
@@ -81,6 +90,7 @@ class AgentSocketClient extends events_1.default {
|
|
|
81
90
|
user: this.user,
|
|
82
91
|
version: this.version,
|
|
83
92
|
activate,
|
|
93
|
+
debug: logger_1.default.debugOn,
|
|
84
94
|
tunnels,
|
|
85
95
|
});
|
|
86
96
|
}
|
|
@@ -18,7 +18,7 @@ class AgentSystem {
|
|
|
18
18
|
serviceMode = false;
|
|
19
19
|
// extend
|
|
20
20
|
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
21
|
-
async install(_id, _host, _token, _port, _start, _user, _pass
|
|
21
|
+
async install(_id, _host, _token, _port, _start, _user, _pass) {
|
|
22
22
|
return Promise.reject();
|
|
23
23
|
}
|
|
24
24
|
/* eslint-enable @typescript-eslint/no-unused-vars */
|
|
@@ -858,9 +858,21 @@ class AgentSystem {
|
|
|
858
858
|
catch {
|
|
859
859
|
output_1.default.exitError(texts_1.ERR_AGENT_NOT_INSTALLED);
|
|
860
860
|
}
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
861
|
+
// Both ways, not only on: the config is what `agent debug` and the remote toggle write,
|
|
862
|
+
// so an agent turned back down starts back down - see the same read in `agent run` for
|
|
863
|
+
// why reading it only when true leaves an install that can never be quietened.
|
|
864
|
+
//
|
|
865
|
+
// The environment still wins upwards, unlike over there. This process is started by
|
|
866
|
+
// hand, so a DEBUG in it was typed by whoever is watching the run - where the one a
|
|
867
|
+
// service inherits is a leftover from the definition an older cli installed. Same OR
|
|
868
|
+
// the level had before this was a config at all: Logger seeds itself from DEBUG, so
|
|
869
|
+
// this only adds the config's ability to state `false`.
|
|
870
|
+
//
|
|
871
|
+
// The one place that assumption does not hold is a container started with `-e DEBUG=1`,
|
|
872
|
+
// where nothing was typed per run: there the remote toggle takes effect immediately but
|
|
873
|
+
// does not survive the container restarting, because the env is back and outranks the
|
|
874
|
+
// config again. Left that way on purpose - an image that asks for debug logs gets them.
|
|
875
|
+
logger_1.default.setDebug(!!json.debug || process.env.DEBUG === '1');
|
|
864
876
|
const saved = this.saveStandaloneProcConfig();
|
|
865
877
|
if (!saved) {
|
|
866
878
|
output_1.default.exitError(texts_1.ERR_SWW_AGENT_ENABLING);
|
|
@@ -116,7 +116,7 @@ class AgentWindows extends system_1.default {
|
|
|
116
116
|
throw err;
|
|
117
117
|
}
|
|
118
118
|
}
|
|
119
|
-
async install(id, host, token, port, start, user, pass
|
|
119
|
+
async install(id, host, token, port, start, user, pass) {
|
|
120
120
|
try {
|
|
121
121
|
// ensure srv was clear
|
|
122
122
|
await this.uninstall();
|
|
@@ -145,7 +145,6 @@ class AgentWindows extends system_1.default {
|
|
|
145
145
|
await this.nssm('set bdy AppRestartDelay 0');
|
|
146
146
|
await this.nssm('set bdy AppExit Default Restart');
|
|
147
147
|
await this.nssm('set bdy Start SERVICE_AUTO_START');
|
|
148
|
-
await this.nssm(`set bdy AppEnvironmentExtra DEBUG=${debug ? 1 : 0}`);
|
|
149
148
|
if (user && pass) {
|
|
150
149
|
await this.nssm(`set bdy ObjectName ${user} ${pass}`);
|
|
151
150
|
await this.grantAccess(user);
|
package/distTs/src/api/client.js
CHANGED
|
@@ -1154,9 +1154,12 @@ class ApiClient {
|
|
|
1154
1154
|
const end = Date.now() + timeout * 1000;
|
|
1155
1155
|
for (;;) {
|
|
1156
1156
|
const { apps } = await this.getSandbox(workspace, sandboxId);
|
|
1157
|
+
// A sandbox with no apps has the field omitted altogether, so an empty
|
|
1158
|
+
// list means there is nothing to wait for - not a timeout.
|
|
1159
|
+
const list = apps || [];
|
|
1157
1160
|
let status = utils_1.SANDBOX_APP_STATUS.RUNNING;
|
|
1158
|
-
for (let i = 0; i <
|
|
1159
|
-
const { app_status } =
|
|
1161
|
+
for (let i = 0; i < list.length; i += 1) {
|
|
1162
|
+
const { app_status } = list[i];
|
|
1160
1163
|
if (app_status === utils_1.SANDBOX_APP_STATUS.FAILED) {
|
|
1161
1164
|
return utils_1.SANDBOX_APP_STATUS.FAILED;
|
|
1162
1165
|
}
|
|
@@ -128,7 +128,7 @@ const installService = async (options) => {
|
|
|
128
128
|
}
|
|
129
129
|
await output_1.default.spinner(texts_1.TXT_ENABLING_AGENT);
|
|
130
130
|
try {
|
|
131
|
-
await AgentManager.system.install(id, host, agentToken, port, !!options.start, options.user, options.pass
|
|
131
|
+
await AgentManager.system.install(id, host, agentToken, port, !!options.start, options.user, options.pass);
|
|
132
132
|
}
|
|
133
133
|
catch {
|
|
134
134
|
await removeAllAndExit(texts_1.ERR_SWW_AGENT_ENABLING, id, host, agentToken);
|
|
@@ -17,6 +17,22 @@ commandAgentRun.action(async (options) => {
|
|
|
17
17
|
// the user it runs as has an `--app` install of their own
|
|
18
18
|
AgentManager.system.serviceMode = true;
|
|
19
19
|
logger_1.default.changeRootPath(AgentManager.system.getNewAgentConfigDir());
|
|
20
|
+
// The level comes from the agent config, which is the only place the two things that
|
|
21
|
+
// change it can write: `agent debug` and the remote DEBUG_ON action both run in a process
|
|
22
|
+
// that cannot rewrite a service definition, so a service reading DEBUG out of its own
|
|
23
|
+
// definition came back from every restart at the level it was installed with.
|
|
24
|
+
//
|
|
25
|
+
// Set unconditionally rather than only when true, so a DEBUG left behind in a definition
|
|
26
|
+
// written by an older cli is overridden by the config instead of being ored with it -
|
|
27
|
+
// otherwise an agent installed with --debug could never be turned back down.
|
|
28
|
+
try {
|
|
29
|
+
logger_1.default.setDebug(!!AgentManager.system.loadSystemConfig().debug);
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
// Nothing readable to take the level from - no install, or a config this process cannot
|
|
33
|
+
// read or parse. Whatever DEBUG the environment carries stands, which for a service is
|
|
34
|
+
// the one written by the cli that installed it.
|
|
35
|
+
}
|
|
20
36
|
AgentManager.start(options.id, options.host, options.token, parseInt(options.port, 10));
|
|
21
37
|
});
|
|
22
38
|
exports.default = commandAgentRun;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const cfg_1 = __importDefault(require("../../tunnel/cfg"));
|
|
7
|
+
const output_1 = __importDefault(require("../../output"));
|
|
8
|
+
const texts_1 = require("../../texts");
|
|
9
|
+
const utils_1 = require("../../utils");
|
|
10
|
+
const commandProjectGet = (0, utils_1.newCommand)('get', texts_1.DESC_COMMAND_PROJECT_GET);
|
|
11
|
+
commandProjectGet.action(async () => {
|
|
12
|
+
const project = cfg_1.default.getProject();
|
|
13
|
+
if (!project) {
|
|
14
|
+
output_1.default.exitError(texts_1.TXT_PROJECT_NONE);
|
|
15
|
+
}
|
|
16
|
+
output_1.default.exitNormal(project);
|
|
17
|
+
});
|
|
18
|
+
exports.default = commandProjectGet;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const cfg_1 = __importDefault(require("../../tunnel/cfg"));
|
|
7
|
+
const output_1 = __importDefault(require("../../output"));
|
|
8
|
+
const texts_1 = require("../../texts");
|
|
9
|
+
const utils_1 = require("../../utils");
|
|
10
|
+
const input_1 = __importDefault(require("../../input"));
|
|
11
|
+
const commandProjectSet = (0, utils_1.newCommand)('set', texts_1.DESC_COMMAND_PROJECT_SET);
|
|
12
|
+
commandProjectSet.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
|
|
13
|
+
commandProjectSet.argument('[project]', texts_1.ARG_COMMAND_PROJECT_NAME);
|
|
14
|
+
commandProjectSet.action(async (project, options) => {
|
|
15
|
+
output_1.default.handleSignals();
|
|
16
|
+
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
17
|
+
const client = input_1.default.restApiTokenClient();
|
|
18
|
+
if (project) {
|
|
19
|
+
await client.getProject(workspace, project);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const response = await client.getProjects(workspace);
|
|
23
|
+
project = await output_1.default.selectProject(response.projects);
|
|
24
|
+
}
|
|
25
|
+
cfg_1.default.setProject(project);
|
|
26
|
+
if (!project)
|
|
27
|
+
output_1.default.exitSuccess(texts_1.TXT_PROJECT_SET_CLEARED);
|
|
28
|
+
else
|
|
29
|
+
output_1.default.exitSuccess((0, texts_1.TXT_PROJECT_SET_SUCCESS)(project));
|
|
30
|
+
});
|
|
31
|
+
exports.default = commandProjectSet;
|
|
@@ -22,11 +22,16 @@ commandSandboxAppList.action(async (identifier, options) => {
|
|
|
22
22
|
if (!sandbox_id) {
|
|
23
23
|
output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
|
|
24
24
|
}
|
|
25
|
-
const
|
|
25
|
+
const sandbox = await client.getSandbox(workspace, sandbox_id);
|
|
26
|
+
// A sandbox with no apps has the field omitted altogether.
|
|
27
|
+
const apps = sandbox.apps || [];
|
|
26
28
|
if (options.format === 'json') {
|
|
27
29
|
output_1.default.json(apps);
|
|
28
30
|
}
|
|
29
31
|
else {
|
|
32
|
+
if (apps.length === 0) {
|
|
33
|
+
output_1.default.exitError(texts_1.ERR_SANDBOX_APPS_NOT_FOUND);
|
|
34
|
+
}
|
|
30
35
|
const table = [['ID', 'COMMAND', 'STATUS']];
|
|
31
36
|
for (const app of apps) {
|
|
32
37
|
let command = app.command;
|
|
@@ -113,8 +113,8 @@ commandSandboxCreate.action(async (options) => {
|
|
|
113
113
|
body.first_boot_commands += '\n';
|
|
114
114
|
body.first_boot_commands += input_1.default.sandboxBootScript(options.bootScript);
|
|
115
115
|
}
|
|
116
|
-
if (options.
|
|
117
|
-
body.tags = options.
|
|
116
|
+
if (options.tag) {
|
|
117
|
+
body.tags = options.tag;
|
|
118
118
|
}
|
|
119
119
|
if (options.appCommand) {
|
|
120
120
|
body.apps = options.appCommand;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const utils_1 = require("../../../utils");
|
|
7
|
+
const texts_1 = require("../../../texts");
|
|
8
|
+
const input_1 = __importDefault(require("../../../input"));
|
|
9
|
+
const output_1 = __importDefault(require("../../../output"));
|
|
10
|
+
const commandSandboxGetYaml = (0, utils_1.newCommand)('yaml', texts_1.DESC_COMMAND_SANDBOX_GET_YAML);
|
|
11
|
+
commandSandboxGetYaml.hideVersionUpdate = true;
|
|
12
|
+
commandSandboxGetYaml.alias('yml');
|
|
13
|
+
commandSandboxGetYaml.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
|
|
14
|
+
commandSandboxGetYaml.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
|
|
15
|
+
commandSandboxGetYaml.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
|
|
16
|
+
commandSandboxGetYaml.action(async (identifier, options) => {
|
|
17
|
+
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
18
|
+
const project = input_1.default.restApiProject(options.project);
|
|
19
|
+
const client = input_1.default.restApiTokenClient();
|
|
20
|
+
let result = await client.listSandboxes(workspace, project);
|
|
21
|
+
const sandboxes = result.sandboxes || [];
|
|
22
|
+
const found = sandboxes.find((s) => s.identifier === identifier);
|
|
23
|
+
if (!found) {
|
|
24
|
+
output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
|
|
25
|
+
}
|
|
26
|
+
const sandboxId = found.id;
|
|
27
|
+
result = await client.getSandboxYaml(workspace, sandboxId);
|
|
28
|
+
output_1.default.exitNormal(Buffer.from(result.yaml, 'base64').toString('utf8'));
|
|
29
|
+
});
|
|
30
|
+
exports.default = commandSandboxGetYaml;
|