@gricha/perry 0.1.5 → 0.1.6

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.
@@ -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 setupWorkspaceCredentials(containerName) {
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;
@@ -347,7 +400,7 @@ export class WorkspaceManager {
347
400
  await this.state.setWorkspace(workspace);
348
401
  await docker.startContainer(containerName);
349
402
  await docker.waitForContainerReady(containerName);
350
- await this.setupWorkspaceCredentials(containerName);
403
+ await this.setupWorkspaceCredentials(containerName, name);
351
404
  workspace.status = 'running';
352
405
  await this.state.setWorkspace(workspace);
353
406
  await this.runPostStartScript(containerName);
@@ -413,7 +466,7 @@ export class WorkspaceManager {
413
466
  }
414
467
  await docker.startContainer(containerName);
415
468
  await docker.waitForContainerReady(containerName);
416
- await this.setupWorkspaceCredentials(containerName);
469
+ await this.setupWorkspaceCredentials(containerName, name);
417
470
  workspace.status = 'running';
418
471
  await this.state.setWorkspace(workspace);
419
472
  await this.runPostStartScript(containerName);
@@ -481,6 +534,6 @@ export class WorkspaceManager {
481
534
  if (!running) {
482
535
  throw new Error(`Workspace '${name}' is not running`);
483
536
  }
484
- await this.setupWorkspaceCredentials(containerName);
537
+ await this.setupWorkspaceCredentials(containerName, name);
485
538
  }
486
539
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gricha/perry",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
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": {