@unionstreet/apple-sandboxes 0.1.0

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/store.js ADDED
@@ -0,0 +1,71 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { storePaths } from './paths.js';
4
+ import { ensureDir, exists, readJson, writeJson } from './util.js';
5
+ const prefixes = {
6
+ sandboxes: 'sbx',
7
+ images: 'img',
8
+ snapshots: 'snap',
9
+ volumes: 'vol',
10
+ };
11
+ export class Store {
12
+ paths;
13
+ constructor(root) {
14
+ this.paths = storePaths(root);
15
+ }
16
+ async init() {
17
+ await Promise.all(Object.values(this.paths).map((dir) => ensureDir(dir)));
18
+ }
19
+ dir(kind, id) {
20
+ return path.join(this.paths[kind], id);
21
+ }
22
+ meta(kind, id) {
23
+ return path.join(this.dir(kind, id), 'meta.json');
24
+ }
25
+ async put(kind, value) {
26
+ await writeJson(this.meta(kind, value.id), value);
27
+ return value;
28
+ }
29
+ async get(kind, idOrName) {
30
+ const direct = this.meta(kind, idOrName);
31
+ if (await exists(direct))
32
+ return readJson(direct);
33
+ for (const item of await this.list(kind)) {
34
+ if (item.name === idOrName)
35
+ return item;
36
+ }
37
+ throw Object.assign(new Error(`${kind.slice(0, -1)} not found`), { status: 404 });
38
+ }
39
+ async list(kind) {
40
+ const dir = this.paths[kind];
41
+ await ensureDir(dir);
42
+ const entries = await fs.readdir(dir, { withFileTypes: true });
43
+ const metas = entries
44
+ .filter((entry) => entry.isDirectory() && entry.name.startsWith(`${prefixes[kind]}_`))
45
+ .map((entry) => path.join(dir, entry.name, 'meta.json'));
46
+ const existing = await Promise.all(metas.map(async (file) => ((await exists(file)) ? file : undefined)));
47
+ const values = await Promise.all(existing.filter(Boolean).map((file) => readJson(file)));
48
+ return values.reverse();
49
+ }
50
+ async delete(kind, idOrName) {
51
+ const item = await this.get(kind, idOrName);
52
+ await fs.rm(this.dir(kind, item.id), { recursive: true, force: true });
53
+ return item.id;
54
+ }
55
+ sandboxWorkspace(id) {
56
+ return path.join(this.dir('sandboxes', id), 'workspace');
57
+ }
58
+ sandboxSnapshots(id) {
59
+ return path.join(this.dir('sandboxes', id), 'snapshots');
60
+ }
61
+ imageContext(id) {
62
+ return path.join(this.dir('images', id), 'context');
63
+ }
64
+ snapshotArchive(id) {
65
+ return path.join(this.dir('snapshots', id), 'workspace.tar.gz');
66
+ }
67
+ volumePath(id) {
68
+ return path.join(this.dir('volumes', id), 'data');
69
+ }
70
+ }
71
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,128 @@
1
+ export type SandboxState = 'creating' | 'created' | 'running' | 'stopped' | 'deleted' | 'error';
2
+ export type NetworkMode = 'deny' | 'open';
3
+ export interface NetworkPolicy {
4
+ mode?: NetworkMode;
5
+ allowedHosts?: string[];
6
+ }
7
+ export interface VolumeMount {
8
+ volumeId: string;
9
+ mountPath: string;
10
+ readonly?: boolean;
11
+ }
12
+ export interface CreateSandboxRequest {
13
+ name?: string;
14
+ image?: string;
15
+ imageId?: string;
16
+ snapshotId?: string;
17
+ env?: Record<string, string>;
18
+ cpus?: number;
19
+ memory?: string;
20
+ network?: boolean | NetworkPolicy;
21
+ autoStart?: boolean;
22
+ idleTimeoutSeconds?: number;
23
+ maxLifetimeSeconds?: number;
24
+ autoDeleteSeconds?: number;
25
+ volumes?: VolumeMount[];
26
+ }
27
+ export interface Sandbox {
28
+ id: string;
29
+ name: string;
30
+ state: SandboxState;
31
+ source: 'inline' | 'image' | 'snapshot';
32
+ image: string;
33
+ imageId?: string;
34
+ snapshotId?: string;
35
+ containerName: string;
36
+ workspace: string;
37
+ cpus: number;
38
+ memory: string;
39
+ network: NetworkPolicy;
40
+ volumes: VolumeMount[];
41
+ createdAt: string;
42
+ updatedAt: string;
43
+ lastActivityAt: string;
44
+ idleTimeoutSeconds?: number;
45
+ maxLifetimeSeconds?: number;
46
+ autoDeleteSeconds?: number;
47
+ ssh?: SshAccess;
48
+ }
49
+ export interface ExecRequest {
50
+ command: string;
51
+ timeoutSeconds?: number;
52
+ env?: Record<string, string>;
53
+ background?: boolean;
54
+ }
55
+ export interface ExecResult {
56
+ id: string;
57
+ sandboxId: string;
58
+ command: string;
59
+ status: 'queued' | 'running' | 'succeeded' | 'failed' | 'timed_out' | 'error';
60
+ exitCode?: number | null;
61
+ stdout?: string;
62
+ stderr?: string;
63
+ createdAt: string;
64
+ startedAt?: string;
65
+ finishedAt?: string;
66
+ }
67
+ export interface CreateImageRequest {
68
+ name: string;
69
+ image?: string;
70
+ dockerfile?: string;
71
+ build?: boolean;
72
+ cpus?: number;
73
+ memory?: string;
74
+ labels?: Record<string, string>;
75
+ }
76
+ export interface ImageDefinition {
77
+ id: string;
78
+ name: string;
79
+ image: string;
80
+ state: 'created' | 'building' | 'ready' | 'error';
81
+ hasDockerfile: boolean;
82
+ cpus: number;
83
+ memory: string;
84
+ labels: Record<string, string>;
85
+ createdAt: string;
86
+ updatedAt: string;
87
+ error?: string;
88
+ }
89
+ export interface CreateSnapshotRequest {
90
+ name: string;
91
+ sandboxId: string;
92
+ labels?: Record<string, string>;
93
+ }
94
+ export interface Snapshot {
95
+ id: string;
96
+ name: string;
97
+ state: 'ready' | 'error';
98
+ sourceSandboxId: string;
99
+ image: string;
100
+ archivePath: string;
101
+ archiveBytes: number;
102
+ workspaceBytes: number;
103
+ labels: Record<string, string>;
104
+ createdAt: string;
105
+ updatedAt: string;
106
+ }
107
+ export interface CreateVolumeRequest {
108
+ name: string;
109
+ labels?: Record<string, string>;
110
+ }
111
+ export interface DeleteVolumeOptions {
112
+ force?: boolean;
113
+ }
114
+ export interface Volume {
115
+ id: string;
116
+ name: string;
117
+ path: string;
118
+ labels: Record<string, string>;
119
+ createdAt: string;
120
+ updatedAt: string;
121
+ }
122
+ export interface SshAccess {
123
+ host: string;
124
+ port: number;
125
+ user: string;
126
+ identityFile?: string;
127
+ command: string;
128
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/dist/util.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ export declare function id(prefix: string): string;
2
+ export declare function now(): string;
3
+ export declare function ensureDir(dir: string): Promise<void>;
4
+ export declare function readJson<T>(file: string): Promise<T>;
5
+ export declare function writeJson(file: string, value: unknown): Promise<void>;
6
+ export declare function exists(file: string): Promise<boolean>;
7
+ export declare function run(cmd: string, args: string[], opts?: {
8
+ cwd?: string;
9
+ timeoutMs?: number;
10
+ }): Promise<{
11
+ exitCode: number | null;
12
+ stdout: string;
13
+ stderr: string;
14
+ }>;
15
+ export declare function safeJoin(root: string, requested: string): string;
package/dist/util.js ADDED
@@ -0,0 +1,63 @@
1
+ import { spawn } from 'node:child_process';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ export function id(prefix) {
5
+ return `${prefix}_${crypto.randomUUID().replaceAll('-', '').slice(0, 16)}`;
6
+ }
7
+ export function now() {
8
+ return new Date().toISOString();
9
+ }
10
+ export async function ensureDir(dir) {
11
+ await fs.mkdir(dir, { recursive: true });
12
+ }
13
+ export async function readJson(file) {
14
+ return JSON.parse(await fs.readFile(file, 'utf8'));
15
+ }
16
+ export async function writeJson(file, value) {
17
+ await ensureDir(path.dirname(file));
18
+ await fs.writeFile(file, `${JSON.stringify(value, null, 2)}\n`);
19
+ }
20
+ export async function exists(file) {
21
+ try {
22
+ await fs.access(file);
23
+ return true;
24
+ }
25
+ catch {
26
+ return false;
27
+ }
28
+ }
29
+ export async function run(cmd, args, opts = {}) {
30
+ const child = spawn(cmd, args, { cwd: opts.cwd, stdio: ['ignore', 'pipe', 'pipe'] });
31
+ const stdout = [];
32
+ const stderr = [];
33
+ const timer = opts.timeoutMs
34
+ ? setTimeout(() => {
35
+ child.kill('SIGKILL');
36
+ }, opts.timeoutMs)
37
+ : undefined;
38
+ child.stdout.on('data', (chunk) => stdout.push(Buffer.from(chunk)));
39
+ child.stderr.on('data', (chunk) => stderr.push(Buffer.from(chunk)));
40
+ const exitCode = await new Promise((resolve) => {
41
+ child.on('error', (error) => {
42
+ stderr.push(Buffer.from(error.message));
43
+ resolve(127);
44
+ });
45
+ child.on('close', resolve);
46
+ });
47
+ if (timer)
48
+ clearTimeout(timer);
49
+ return {
50
+ exitCode,
51
+ stdout: Buffer.concat(stdout).toString('utf8'),
52
+ stderr: Buffer.concat(stderr).toString('utf8'),
53
+ };
54
+ }
55
+ export function safeJoin(root, requested) {
56
+ const target = path.resolve(root, requested.replace(/^\/+/, ''));
57
+ const resolvedRoot = path.resolve(root);
58
+ if (target !== resolvedRoot && !target.startsWith(`${resolvedRoot}${path.sep}`)) {
59
+ throw new Error('path escapes root');
60
+ }
61
+ return target;
62
+ }
63
+ //# sourceMappingURL=util.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../src/util.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,130 @@
1
+ # Architecture
2
+
3
+ `apple-sandboxes` is a local control plane for Apple's native `container` runtime.
4
+
5
+ The shape is inspired by systems like Gondolin and Daytona, but the implementation is intentionally Apple-container-native:
6
+
7
+ - no Docker daemon
8
+ - no Linux host requirement
9
+ - no microVM guest image pipeline
10
+ - no rootful daemon
11
+
12
+ ## Primitives
13
+
14
+ ### Image
15
+
16
+ An image is a named runtime definition.
17
+
18
+ It can be:
19
+
20
+ - a reference to an existing OCI image, such as `ubuntu:24.04`
21
+ - a Dockerfile/Containerfile stored in the local registry and built with `container build`
22
+
23
+ Images are stored under:
24
+
25
+ ```text
26
+ ~/.apple-sandboxes/images
27
+ ```
28
+
29
+ ### Sandbox
30
+
31
+ A sandbox is a long-lived Apple container with:
32
+
33
+ - a host-backed `/workspace`
34
+ - optional named volume mounts
35
+ - CPU/memory limits
36
+ - a network policy
37
+ - lifecycle metadata
38
+
39
+ Sandboxes are stored under:
40
+
41
+ ```text
42
+ ~/.apple-sandboxes/sandboxes
43
+ ```
44
+
45
+ ### Snapshot
46
+
47
+ A snapshot is a reusable `/workspace` archive.
48
+
49
+ Creating a new sandbox from a snapshot restores the archive into a fresh sandbox workspace before the container starts.
50
+
51
+ Snapshots are stored under:
52
+
53
+ ```text
54
+ ~/.apple-sandboxes/snapshots
55
+ ```
56
+
57
+ This is a workspace snapshot, not a VM checkpoint and not a full root filesystem snapshot.
58
+
59
+ ### Volume
60
+
61
+ A volume is a named persistent host directory mounted into one or more sandboxes.
62
+
63
+ Volumes are stored under:
64
+
65
+ ```text
66
+ ~/.apple-sandboxes/volumes
67
+ ```
68
+
69
+ ## Runtime Model
70
+
71
+ Sandbox creation maps to Apple `container create`:
72
+
73
+ ```text
74
+ container create \
75
+ --platform linux/arm64 \
76
+ --cpus N \
77
+ --memory SIZE \
78
+ --mount type=bind,source=WORKSPACE,target=/workspace \
79
+ --workdir /workspace \
80
+ IMAGE \
81
+ sh -lc 'while true; do sleep 3600; done'
82
+ ```
83
+
84
+ Command execution maps to:
85
+
86
+ ```text
87
+ container exec -w /workspace CONTAINER sh -lc COMMAND
88
+ ```
89
+
90
+ ## Network Model
91
+
92
+ The first implementation supports:
93
+
94
+ - `deny`: maps to `--network none`
95
+ - `open`: leaves the default Apple container network enabled
96
+
97
+ The intended next step is a host-mediated allowlist model:
98
+
99
+ ```json
100
+ {
101
+ "network": {
102
+ "mode": "allowlist",
103
+ "allowedHosts": ["api.github.com"]
104
+ }
105
+ }
106
+ ```
107
+
108
+ That is closer to Gondolin's policy direction, but is not implemented yet.
109
+
110
+ ## SSH Model
111
+
112
+ SSH is not implemented in this first cut.
113
+
114
+ The intended shape is:
115
+
116
+ - generate ephemeral Ed25519 keys
117
+ - inject the public key into the sandbox
118
+ - run `sshd` inside a prepared sandbox image
119
+ - bind only to a localhost host port
120
+ - return a hardened SSH command with agent/forwarding disabled
121
+
122
+ ## API Layers
123
+
124
+ The package exposes:
125
+
126
+ - TypeScript runtime class: `AppleSandboxRuntime`
127
+ - TypeScript HTTP client: `AppleSandboxes`
128
+ - Express API server
129
+ - npm CLI: `apple-sandboxes`
130
+
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@unionstreet/apple-sandboxes",
3
+ "version": "0.1.0",
4
+ "description": "Daytona/Gondolin-inspired sandbox API for Apple container on Apple Silicon Macs.",
5
+ "type": "module",
6
+ "bin": {
7
+ "apple-sandboxes": "dist/cli.js"
8
+ },
9
+ "exports": {
10
+ ".": "./dist/index.js"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "docs",
15
+ "README.md",
16
+ "LICENSE"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsx src/cli.ts",
21
+ "start": "node dist/cli.js serve",
22
+ "test": "tsx --test test/**/*.test.ts",
23
+ "typecheck": "tsc --noEmit",
24
+ "prepack": "npm run build",
25
+ "prepublishOnly": "npm run typecheck && npm run test"
26
+ },
27
+ "keywords": [
28
+ "apple-container",
29
+ "sandbox",
30
+ "agents",
31
+ "local-first",
32
+ "macos"
33
+ ],
34
+ "license": "Apache-2.0",
35
+ "homepage": "https://github.com/UnionStreetAI/apple-sandboxes#readme",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/UnionStreetAI/apple-sandboxes.git"
39
+ },
40
+ "bugs": {
41
+ "url": "https://github.com/UnionStreetAI/apple-sandboxes/issues"
42
+ },
43
+ "publishConfig": {
44
+ "access": "public"
45
+ },
46
+ "engines": {
47
+ "node": ">=20"
48
+ },
49
+ "dependencies": {
50
+ "commander": "^14.0.2",
51
+ "express": "^5.1.0"
52
+ },
53
+ "devDependencies": {
54
+ "@types/express": "^5.0.5",
55
+ "@types/node": "^24.10.1",
56
+ "tsx": "^4.21.0",
57
+ "typescript": "^5.9.3"
58
+ }
59
+ }