@gricha/perry 0.1.5 → 0.1.7
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/dist/agent/router.js +72 -34
- package/dist/agent/run.js +3 -0
- package/dist/agent/web/assets/index-BTiTEcB0.js +104 -0
- package/dist/agent/web/assets/index-D2_-UqVf.css +1 -0
- package/dist/agent/web/index.html +2 -2
- package/dist/config/loader.js +16 -0
- package/dist/index.js +185 -1
- package/dist/sessions/cache.js +52 -0
- package/dist/ssh/discovery.js +96 -0
- package/dist/ssh/index.js +2 -0
- package/dist/ssh/sync.js +80 -0
- package/dist/workspace/manager.js +63 -4
- package/dist/workspace/state.js +9 -0
- package/package.json +3 -2
- package/dist/agent/web/assets/index-BGbqUzMS.js +0 -104
- package/dist/agent/web/assets/index-CHEQQv1U.css +0 -1
|
@@ -8,6 +8,7 @@ import { expandPath } from '../config/loader';
|
|
|
8
8
|
import * as docker from '../docker';
|
|
9
9
|
import { getContainerName } from '../docker';
|
|
10
10
|
import { VOLUME_PREFIX, WORKSPACE_IMAGE_LOCAL, WORKSPACE_IMAGE_REGISTRY, SSH_PORT_RANGE_START, SSH_PORT_RANGE_END, } from '../shared/constants';
|
|
11
|
+
import { collectAuthorizedKeys, collectCopyKeys } from '../ssh/sync';
|
|
11
12
|
async function findAvailablePort(start, end) {
|
|
12
13
|
for (let port = start; port <= end; port++) {
|
|
13
14
|
const available = await new Promise((resolve) => {
|
|
@@ -226,12 +227,64 @@ export class WorkspaceManager {
|
|
|
226
227
|
tempPrefix: 'ws-gitconfig',
|
|
227
228
|
});
|
|
228
229
|
}
|
|
229
|
-
async
|
|
230
|
+
async setupSSHKeys(containerName, workspaceName) {
|
|
231
|
+
if (!this.config.ssh) {
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
await docker.execInContainer(containerName, ['mkdir', '-p', '/home/workspace/.ssh'], {
|
|
235
|
+
user: 'workspace',
|
|
236
|
+
});
|
|
237
|
+
await docker.execInContainer(containerName, ['chmod', '700', '/home/workspace/.ssh'], {
|
|
238
|
+
user: 'workspace',
|
|
239
|
+
});
|
|
240
|
+
const authorizedKeys = await collectAuthorizedKeys(this.config.ssh, workspaceName);
|
|
241
|
+
if (authorizedKeys.length > 0) {
|
|
242
|
+
const content = authorizedKeys.join('\n') + '\n';
|
|
243
|
+
const tempFile = path.join(os.tmpdir(), `ws-authkeys-${Date.now()}`);
|
|
244
|
+
try {
|
|
245
|
+
await fs.writeFile(tempFile, content);
|
|
246
|
+
await docker.copyToContainer(containerName, tempFile, '/home/workspace/.ssh/authorized_keys');
|
|
247
|
+
await docker.execInContainer(containerName, ['chown', 'workspace:workspace', '/home/workspace/.ssh/authorized_keys'], { user: 'root' });
|
|
248
|
+
await docker.execInContainer(containerName, ['chmod', '600', '/home/workspace/.ssh/authorized_keys'], { user: 'workspace' });
|
|
249
|
+
}
|
|
250
|
+
finally {
|
|
251
|
+
await fs.unlink(tempFile).catch(() => { });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
const copyKeys = await collectCopyKeys(this.config.ssh, workspaceName);
|
|
255
|
+
for (const key of copyKeys) {
|
|
256
|
+
const privateKeyPath = `/home/workspace/.ssh/${key.name}`;
|
|
257
|
+
const publicKeyPath = `/home/workspace/.ssh/${key.name}.pub`;
|
|
258
|
+
const privateTempFile = path.join(os.tmpdir(), `ws-privkey-${Date.now()}`);
|
|
259
|
+
const publicTempFile = path.join(os.tmpdir(), `ws-pubkey-${Date.now()}`);
|
|
260
|
+
try {
|
|
261
|
+
await fs.writeFile(privateTempFile, key.privateKey + '\n');
|
|
262
|
+
await fs.writeFile(publicTempFile, key.publicKey + '\n');
|
|
263
|
+
await docker.copyToContainer(containerName, privateTempFile, privateKeyPath);
|
|
264
|
+
await docker.copyToContainer(containerName, publicTempFile, publicKeyPath);
|
|
265
|
+
await docker.execInContainer(containerName, ['chown', 'workspace:workspace', privateKeyPath, publicKeyPath], { user: 'root' });
|
|
266
|
+
await docker.execInContainer(containerName, ['chmod', '600', privateKeyPath], {
|
|
267
|
+
user: 'workspace',
|
|
268
|
+
});
|
|
269
|
+
await docker.execInContainer(containerName, ['chmod', '644', publicKeyPath], {
|
|
270
|
+
user: 'workspace',
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
finally {
|
|
274
|
+
await fs.unlink(privateTempFile).catch(() => { });
|
|
275
|
+
await fs.unlink(publicTempFile).catch(() => { });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
async setupWorkspaceCredentials(containerName, workspaceName) {
|
|
230
280
|
await this.copyGitConfig(containerName);
|
|
231
281
|
await this.copyCredentialFiles(containerName);
|
|
232
282
|
await this.setupClaudeCodeConfig(containerName);
|
|
233
283
|
await this.copyCodexCredentials(containerName);
|
|
234
284
|
await this.setupOpencodeConfig(containerName);
|
|
285
|
+
if (workspaceName) {
|
|
286
|
+
await this.setupSSHKeys(containerName, workspaceName);
|
|
287
|
+
}
|
|
235
288
|
}
|
|
236
289
|
async runPostStartScript(containerName) {
|
|
237
290
|
const scriptPath = this.config.scripts.post_start;
|
|
@@ -290,6 +343,9 @@ export class WorkspaceManager {
|
|
|
290
343
|
await this.syncWorkspaceStatus(workspace);
|
|
291
344
|
return workspace;
|
|
292
345
|
}
|
|
346
|
+
async touch(name) {
|
|
347
|
+
return this.state.touchWorkspace(name);
|
|
348
|
+
}
|
|
293
349
|
async create(options) {
|
|
294
350
|
const { name, clone, env } = options;
|
|
295
351
|
const containerName = getContainerName(name);
|
|
@@ -307,6 +363,7 @@ export class WorkspaceManager {
|
|
|
307
363
|
ports: {
|
|
308
364
|
ssh: 0,
|
|
309
365
|
},
|
|
366
|
+
lastUsed: new Date().toISOString(),
|
|
310
367
|
};
|
|
311
368
|
await this.state.setWorkspace(workspace);
|
|
312
369
|
try {
|
|
@@ -347,7 +404,7 @@ export class WorkspaceManager {
|
|
|
347
404
|
await this.state.setWorkspace(workspace);
|
|
348
405
|
await docker.startContainer(containerName);
|
|
349
406
|
await docker.waitForContainerReady(containerName);
|
|
350
|
-
await this.setupWorkspaceCredentials(containerName);
|
|
407
|
+
await this.setupWorkspaceCredentials(containerName, name);
|
|
351
408
|
workspace.status = 'running';
|
|
352
409
|
await this.state.setWorkspace(workspace);
|
|
353
410
|
await this.runPostStartScript(containerName);
|
|
@@ -408,13 +465,15 @@ export class WorkspaceManager {
|
|
|
408
465
|
const running = await docker.containerRunning(containerName);
|
|
409
466
|
if (running) {
|
|
410
467
|
workspace.status = 'running';
|
|
468
|
+
workspace.lastUsed = new Date().toISOString();
|
|
411
469
|
await this.state.setWorkspace(workspace);
|
|
412
470
|
return workspace;
|
|
413
471
|
}
|
|
414
472
|
await docker.startContainer(containerName);
|
|
415
473
|
await docker.waitForContainerReady(containerName);
|
|
416
|
-
await this.setupWorkspaceCredentials(containerName);
|
|
474
|
+
await this.setupWorkspaceCredentials(containerName, name);
|
|
417
475
|
workspace.status = 'running';
|
|
476
|
+
workspace.lastUsed = new Date().toISOString();
|
|
418
477
|
await this.state.setWorkspace(workspace);
|
|
419
478
|
await this.runPostStartScript(containerName);
|
|
420
479
|
return workspace;
|
|
@@ -481,6 +540,6 @@ export class WorkspaceManager {
|
|
|
481
540
|
if (!running) {
|
|
482
541
|
throw new Error(`Workspace '${name}' is not running`);
|
|
483
542
|
}
|
|
484
|
-
await this.setupWorkspaceCredentials(containerName);
|
|
543
|
+
await this.setupWorkspaceCredentials(containerName, name);
|
|
485
544
|
}
|
|
486
545
|
}
|
package/dist/workspace/state.js
CHANGED
|
@@ -94,4 +94,13 @@ export class StateManager {
|
|
|
94
94
|
await this.setWorkspace(workspace);
|
|
95
95
|
return workspace;
|
|
96
96
|
}
|
|
97
|
+
async touchWorkspace(name) {
|
|
98
|
+
const workspace = await this.getWorkspace(name);
|
|
99
|
+
if (!workspace) {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
workspace.lastUsed = new Date().toISOString();
|
|
103
|
+
await this.setWorkspace(workspace);
|
|
104
|
+
return workspace;
|
|
105
|
+
}
|
|
97
106
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gricha/perry",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.7",
|
|
4
4
|
"description": "Self-contained CLI for spinning up Docker-in-Docker development environments with SSH and proxy helpers.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -22,7 +22,8 @@
|
|
|
22
22
|
"format": "oxfmt --write src/ test/",
|
|
23
23
|
"format:check": "oxfmt --check src/ test/",
|
|
24
24
|
"check": "bun run lint && bun run format:check && bun x tsc --noEmit",
|
|
25
|
-
"
|
|
25
|
+
"lint:web": "cd web && bun run lint",
|
|
26
|
+
"validate": "bun run check && bun run build && bun run test && bun run lint:web && bun run test:web"
|
|
26
27
|
},
|
|
27
28
|
"engines": {
|
|
28
29
|
"bun": ">=1.3.5"
|