Skip to content

Commit 312c8b3

Browse files
committed
feat: macOS port (merge #111) + /workspace + /home/agentos + binding facade + 4-target publish
Also fix publish vfs-bump bug (FALLING_OUT.md §4a): bumpCargoVersions only matched agent-os-*/secure-exec-* path deps, silently skipping the unprefixed `vfs` workspace crate -> dependent stayed pinned to ^0.3.0-rc.1 while the crate moved to the preview version, failing cargo resolution in the publish build. Regex now bumps every internal `path = "...", version = "..."` dep. Also gate publish-crates to release-only (FALLING_OUT.md §4b): on previews the crates dry-run compiled/packaged and any error reddened the whole run even though npm published fine. build-sidecar already compiles every crate per target.
1 parent 0617da2 commit 312c8b3

42 files changed

Lines changed: 505 additions & 284 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/publish.yaml

Lines changed: 95 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ concurrency:
2020
env:
2121
R2_BUCKET: rivet-releases
2222
R2_ENDPOINT: https://2a94c6a0ced8d35ea63cddc86c2681e7.r2.cloudflarestorage.com
23-
SIDECAR_PLATFORMS: "linux-x64-gnu"
23+
SIDECAR_PLATFORMS: "linux-x64-gnu linux-arm64-gnu darwin-x64 darwin-arm64"
2424

2525
jobs:
2626
context:
@@ -36,10 +36,10 @@ jobs:
3636
- uses: actions/checkout@v4
3737
with:
3838
fetch-depth: 1
39-
- uses: pnpm/action-setup@v4
39+
- run: corepack enable
4040
- uses: actions/setup-node@v4
4141
with:
42-
node-version: 22
42+
node-version: "22"
4343
cache: pnpm
4444
cache-dependency-path: pnpm-lock.yaml
4545
- name: Install publish scripts
@@ -53,23 +53,51 @@ jobs:
5353
name: "Build sidecar (${{ matrix.platform }})"
5454
strategy:
5555
fail-fast: false
56+
# The target set is dictated by the embedded V8 (rusty_v8): the build links
57+
# a prebuilt `librusty_v8` static lib, and rusty_v8 publishes prebuilts for
58+
# EXACTLY these four triples (linux-gnu + darwin, x64/arm64). musl has no
59+
# prebuilt (would force a 30+ min V8 source build). Keep this list in
60+
# lockstep with the rusty_v8 release assets when bumping the v8 crate.
5661
matrix:
5762
include:
5863
- platform: linux-x64-gnu
5964
runner: ubuntu-22.04
6065
target: x86_64-unknown-linux-gnu
66+
binary: secure-exec-sidecar
67+
- platform: linux-arm64-gnu
68+
runner: ubuntu-22.04-arm
69+
target: aarch64-unknown-linux-gnu
70+
binary: secure-exec-sidecar
71+
- platform: darwin-x64
72+
runner: macos-13
73+
target: x86_64-apple-darwin
74+
binary: secure-exec-sidecar
75+
- platform: darwin-arm64
76+
runner: macos-14
77+
target: aarch64-apple-darwin
78+
binary: secure-exec-sidecar
6179
runs-on: ${{ matrix.runner }}
6280
steps:
6381
- uses: actions/checkout@v4
64-
- uses: pnpm/action-setup@v4
82+
- run: corepack enable
83+
shell: bash
6584
- uses: actions/setup-node@v4
6685
with:
67-
node-version: 22
86+
node-version: "22"
6887
cache: pnpm
6988
cache-dependency-path: pnpm-lock.yaml
7089
- uses: dtolnay/rust-toolchain@stable
71-
with:
72-
targets: ${{ matrix.target }}
90+
- name: Add Rust target to the pinned toolchain
91+
shell: bash
92+
run: |
93+
set -euo pipefail
94+
# The repo pins a toolchain via rust-toolchain.toml; cross targets
95+
# must be added to *that* toolchain (not just `stable`), or cargo
96+
# fails with "can't find crate for `core`" for the cross target.
97+
channel=$(awk -F'"' '/channel/ {print $2; exit}' rust-toolchain.toml)
98+
echo "Pinned toolchain channel: ${channel}"
99+
rustup toolchain install "${channel}" --profile minimal
100+
rustup target add --toolchain "${channel}" "${{ matrix.target }}"
73101
- uses: Swatinem/rust-cache@v2
74102
with:
75103
workspaces: . -> target
@@ -80,21 +108,35 @@ jobs:
80108
key: ${{ runner.os }}-${{ matrix.target }}-rusty-v8-${{ hashFiles('Cargo.lock') }}
81109
restore-keys: |
82110
${{ runner.os }}-${{ matrix.target }}-rusty-v8-
83-
- run: pnpm install --frozen-lockfile
111+
# The v8-runtime build script generates the V8 bridge assets from
112+
# packages/build-tools/node_modules, so the workspace must be installed
113+
# before cargo build. Exclude the `website` package: it is a leaf nothing
114+
# depends on, and pnpm 10.x intermittently loses a symlink-ordering race
115+
# creating website/node_modules (ENOENT before linking pixelmatch).
116+
- name: Install workspace dependencies
117+
shell: bash
118+
run: pnpm install --frozen-lockfile --filter='!@secure-exec/website'
84119
- name: Build sidecar binary
85120
id: build
121+
shell: bash
122+
env:
123+
MATRIX_TARGET: ${{ matrix.target }}
124+
MATRIX_PLATFORM: ${{ matrix.platform }}
125+
MATRIX_BINARY: ${{ matrix.binary }}
126+
TRIGGER: ${{ needs.context.outputs.trigger }}
86127
run: |
87128
set -euo pipefail
88-
out="target/sidecar-artifacts/${{ matrix.platform }}"
129+
130+
out="target/sidecar-artifacts/${MATRIX_PLATFORM}"
89131
mkdir -p "$out"
90-
if [ "${{ needs.context.outputs.trigger }}" = "release" ]; then
91-
cargo build --release -p secure-exec-sidecar --target ${{ matrix.target }}
132+
if [ "$TRIGGER" = "release" ]; then
133+
cargo build --release -p secure-exec-sidecar --target "$MATRIX_TARGET"
92134
profile="release"
93135
else
94-
cargo build -p secure-exec-sidecar --target ${{ matrix.target }}
136+
cargo build -p secure-exec-sidecar --target "$MATRIX_TARGET"
95137
profile="debug"
96138
fi
97-
cp "target/${{ matrix.target }}/${profile}/secure-exec-sidecar" "$out/secure-exec-sidecar"
139+
cp "target/${MATRIX_TARGET}/${profile}/${MATRIX_BINARY}" "$out/${MATRIX_BINARY}"
98140
echo "dir=$out" >> "$GITHUB_OUTPUT"
99141
- uses: actions/upload-artifact@v4
100142
with:
@@ -114,14 +156,21 @@ jobs:
114156
runs-on: ubuntu-latest
115157
steps:
116158
- uses: actions/checkout@v4
117-
- uses: pnpm/action-setup@v4
159+
- run: corepack enable
118160
- uses: actions/setup-node@v4
119161
with:
120-
node-version: 22
162+
node-version: "22"
121163
cache: pnpm
122164
cache-dependency-path: pnpm-lock.yaml
123165
registry-url: https://registry.npmjs.org
124-
- run: pnpm install --frozen-lockfile
166+
# Install the full workspace except the `website` package. website is a
167+
# leaf (nothing depends on it, and it is not published here), and pnpm 10.x
168+
# intermittently loses a symlink-ordering race creating website/node_modules
169+
# (ENOENT before linking pixelmatch). Excluding it deterministically avoids
170+
# the flake. `--frozen-lockfile` still validates the full lockfile.
171+
- name: Install workspace dependencies
172+
shell: bash
173+
run: pnpm install --frozen-lockfile --filter='!@secure-exec/website'
125174
- uses: actions/download-artifact@v4
126175
with:
127176
pattern: sidecar-*
@@ -130,7 +179,8 @@ jobs:
130179
run: |
131180
set -euo pipefail
132181
for p in $SIDECAR_PLATFORMS; do
133-
bin="artifacts/sidecar-${p}/secure-exec-sidecar"
182+
binname="secure-exec-sidecar"
183+
bin="artifacts/sidecar-${p}/${binname}"
134184
dest="packages/sidecar/npm/${p}"
135185
if [ ! -f "$bin" ]; then
136186
echo "::error::missing secure-exec-sidecar binary artifact for ${p}"
@@ -140,8 +190,8 @@ jobs:
140190
echo "::error::missing platform package dir $dest"
141191
exit 1
142192
fi
143-
cp "$bin" "${dest}/secure-exec-sidecar"
144-
chmod +x "${dest}/secure-exec-sidecar"
193+
cp "$bin" "${dest}/${binname}"
194+
chmod +x "${dest}/${binname}"
145195
echo "Placed binary for ${p}"
146196
done
147197
- name: Bump package versions for build
@@ -152,7 +202,8 @@ jobs:
152202
- name: Build TypeScript packages
153203
run: |
154204
npx turbo build \
155-
--filter='!./examples/*'
205+
--filter='!./examples/*' \
206+
--filter='!@secure-exec/website'
156207
- name: Finalize package versions for publish
157208
run: |
158209
pnpm --filter=publish exec tsx src/ci/bin.ts bump-versions \
@@ -178,10 +229,10 @@ jobs:
178229
- uses: actions/checkout@v4
179230
with:
180231
fetch-depth: 0
181-
- uses: pnpm/action-setup@v4
232+
- run: corepack enable
182233
- uses: actions/setup-node@v4
183234
with:
184-
node-version: 22
235+
node-version: "22"
185236
cache: pnpm
186237
cache-dependency-path: pnpm-lock.yaml
187238
- run: pnpm install --frozen-lockfile --filter=publish
@@ -196,13 +247,18 @@ jobs:
196247
set -euo pipefail
197248
declare -A PLATFORM_TARGET=(
198249
[linux-x64-gnu]=x86_64-unknown-linux-gnu
250+
[linux-arm64-gnu]=aarch64-unknown-linux-gnu
251+
[darwin-x64]=x86_64-apple-darwin
252+
[darwin-arm64]=aarch64-apple-darwin
199253
)
200254
mkdir -p release-assets
201255
for p in $SIDECAR_PLATFORMS; do
202-
bin="artifacts/sidecar-${p}/secure-exec-sidecar"
256+
binname="secure-exec-sidecar"
257+
suffix=""
258+
bin="artifacts/sidecar-${p}/${binname}"
203259
if [ -f "$bin" ]; then
204260
target="${PLATFORM_TARGET[$p]}"
205-
cp "$bin" "release-assets/secure-exec-sidecar-${target}"
261+
cp "$bin" "release-assets/secure-exec-sidecar-${target}${suffix}"
206262
else
207263
echo "::warning::missing secure-exec-sidecar binary for ${p}"
208264
fi
@@ -274,14 +330,18 @@ jobs:
274330
publish-crates:
275331
needs: [context, build-sidecar, release-assets]
276332
name: "Publish crates.io"
277-
if: ${{ !cancelled() && needs.build-sidecar.result == 'success' && (needs.release-assets.result == 'success' || needs.release-assets.result == 'skipped') }}
333+
# Release-only: on previews the crates publish is a dry-run that still does a
334+
# full compile/package, and any error there reddened the WHOLE run even though
335+
# the npm deliverable published fine (FALLING_OUT.md §4b). The 4 build-sidecar
336+
# legs already compile every crate, so the preview dry-run added only noise.
337+
if: ${{ !cancelled() && needs.build-sidecar.result == 'success' && needs.release-assets.result == 'success' && needs.context.outputs.trigger == 'release' }}
278338
runs-on: ubuntu-latest
279339
steps:
280340
- uses: actions/checkout@v4
281-
- uses: pnpm/action-setup@v4
341+
- run: corepack enable
282342
- uses: actions/setup-node@v4
283343
with:
284-
node-version: 22
344+
node-version: "22"
285345
cache: pnpm
286346
cache-dependency-path: pnpm-lock.yaml
287347
- uses: dtolnay/rust-toolchain@stable
@@ -294,7 +354,14 @@ jobs:
294354
key: ${{ runner.os }}-x86_64-unknown-linux-gnu-rusty-v8-${{ hashFiles('Cargo.lock') }}
295355
restore-keys: |
296356
${{ runner.os }}-x86_64-unknown-linux-gnu-rusty-v8-
297-
- run: pnpm install --frozen-lockfile
357+
# Install the full workspace except the `website` package. website is a
358+
# leaf (nothing depends on it, and it is not published here), and pnpm 10.x
359+
# intermittently loses a symlink-ordering race creating website/node_modules
360+
# (ENOENT before linking pixelmatch). Excluding it deterministically avoids
361+
# the flake. `--frozen-lockfile` still validates the full lockfile.
362+
- name: Install workspace dependencies
363+
shell: bash
364+
run: pnpm install --frozen-lockfile --filter='!@secure-exec/website'
298365
- name: Bump Cargo versions
299366
run: |
300367
pnpm --filter=publish exec tsx src/ci/bin.ts bump-versions \

Cargo.lock

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ npm install secure-exec
5252

5353
**2. Create a runtime**
5454

55-
`NodeRuntime.create()` boots a fully virtualized VM behind the native sidecar. Guest code runs inside the kernel isolation boundary with no host escapes. All options are optional: `cwd` defaults to `/home/user`, and permissions default to a secure policy that denies network access (see step 4).
55+
`NodeRuntime.create()` boots a fully virtualized VM behind the native sidecar. Guest code runs inside the kernel isolation boundary with no host escapes. All options are optional: `cwd` defaults to `/workspace`, and permissions default to a secure policy that denies network access (see step 4).
5656

5757
```ts
5858
import { NodeRuntime } from "secure-exec";

crates/execution/src/wasm.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5631,13 +5631,13 @@ mod tests {
56315631
fn translate_wasm_guest_path_uses_sandbox_root_for_absolute_paths() {
56325632
let temp = tempdir().expect("create temp dir");
56335633
let sandbox_root = temp.path().join("shadow-root");
5634-
let cwd = sandbox_root.join("home/user");
5634+
let cwd = sandbox_root.join("workspace");
56355635
fs::create_dir_all(cwd.join("project")).expect("create host cwd");
56365636

56375637
let internal_sync_rpc = WasmInternalSyncRpc {
56385638
module_guest_paths: Vec::new(),
56395639
module_host_path: sandbox_root.join("module.wasm"),
5640-
guest_cwd: String::from("/home/user"),
5640+
guest_cwd: String::from("/workspace"),
56415641
host_cwd: cwd.clone(),
56425642
sandbox_root: Some(sandbox_root.clone()),
56435643
guest_path_mappings: Vec::new(),
@@ -5660,13 +5660,13 @@ mod tests {
56605660
fn translate_wasm_host_symlink_target_returns_guest_path_for_mapped_targets() {
56615661
let temp = tempdir().expect("create temp dir");
56625662
let sandbox_root = temp.path().join("shadow-root");
5663-
let cwd = sandbox_root.join("home/user");
5663+
let cwd = sandbox_root.join("workspace");
56645664
fs::create_dir_all(cwd.join("project")).expect("create host cwd");
56655665

56665666
let internal_sync_rpc = WasmInternalSyncRpc {
56675667
module_guest_paths: Vec::new(),
56685668
module_host_path: sandbox_root.join("module.wasm"),
5669-
guest_cwd: String::from("/home/user"),
5669+
guest_cwd: String::from("/workspace"),
56705670
host_cwd: cwd.clone(),
56715671
sandbox_root: Some(sandbox_root.clone()),
56725672
guest_path_mappings: vec![super::WasmGuestPathMapping {
@@ -5983,13 +5983,13 @@ mod tests {
59835983
fn wasm_guest_path_mappings_mount_root_to_sandbox_root() {
59845984
let temp = tempdir().expect("create temp dir");
59855985
let sandbox_root = temp.path().join("shadow-root");
5986-
let host_cwd = sandbox_root.join("home/user");
5986+
let host_cwd = sandbox_root.join("workspace");
59875987
fs::create_dir_all(&host_cwd).expect("create host cwd");
59885988

59895989
let mappings = super::wasm_guest_path_mappings(&request_with_env(
59905990
&host_cwd,
59915991
BTreeMap::from([
5992-
(String::from("PWD"), String::from("/home/user")),
5992+
(String::from("PWD"), String::from("/workspace")),
59935993
(
59945994
String::from(WASM_SANDBOX_ROOT_ENV),
59955995
sandbox_root.to_string_lossy().into_owned(),
@@ -6001,7 +6001,7 @@ mod tests {
60016001
.iter()
60026002
.any(|mapping| { mapping.guest_path == "/" && mapping.host_path == sandbox_root }));
60036003
assert!(mappings.iter().any(|mapping| {
6004-
mapping.guest_path == "/home/user" && mapping.host_path == host_cwd
6004+
mapping.guest_path == "/workspace" && mapping.host_path == host_cwd
60056005
}));
60066006
}
60076007

crates/kernel/src/kernel.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ impl KernelVmConfig {
127127
Self {
128128
vm_id: vm_id.into(),
129129
env: BTreeMap::new(),
130-
cwd: String::from("/home/user"),
130+
cwd: String::from("/workspace"),
131131
user: UserConfig::default(),
132132
permissions: Permissions::default(),
133133
dns: DnsConfig::default(),

crates/kernel/src/user.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ impl UserManager {
6363
pub fn from_config(config: UserConfig) -> Self {
6464
let uid = config.uid.unwrap_or(1000);
6565
let gid = config.gid.unwrap_or(1000);
66-
let username = config.username.unwrap_or_else(|| String::from("user"));
66+
let username = config.username.unwrap_or_else(|| String::from("agentos"));
6767
let supplementary_gids = normalize_supplementary_gids(gid, config.supplementary_gids);
6868

6969
Self {
@@ -72,7 +72,7 @@ impl UserManager {
7272
euid: config.euid.unwrap_or(uid),
7373
egid: config.egid.unwrap_or(gid),
7474
username: username.clone(),
75-
homedir: config.homedir.unwrap_or_else(|| String::from("/home/user")),
75+
homedir: config.homedir.unwrap_or_else(|| String::from("/home/agentos")),
7676
shell: config.shell.unwrap_or_else(|| String::from("/bin/sh")),
7777
gecos: config.gecos.unwrap_or_default(),
7878
group_name: config.group_name.unwrap_or(username),

crates/kernel/tests/permissions.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -307,13 +307,13 @@ fn filter_env_only_keeps_allowed_keys() {
307307
};
308308

309309
let env = BTreeMap::from([
310-
(String::from("HOME"), String::from("/home/user")),
310+
(String::from("HOME"), String::from("/home/agentos")),
311311
(String::from("PATH"), String::from("/usr/bin")),
312312
(String::from("SECRET_KEY"), String::from("hidden")),
313313
]);
314314

315315
let filtered = filter_env("vm-permissions", &env, &permissions);
316-
assert_eq!(filtered.get("HOME"), Some(&String::from("/home/user")));
316+
assert_eq!(filtered.get("HOME"), Some(&String::from("/home/agentos")));
317317
assert_eq!(filtered.get("PATH"), Some(&String::from("/usr/bin")));
318318
assert!(!filtered.contains_key("SECRET_KEY"));
319319
}
@@ -408,7 +408,7 @@ fn kernel_vm_config_defaults_to_deny_all_permissions() {
408408
}
409409

410410
#[test]
411-
fn kernel_default_spawn_cwd_matches_home_user() {
411+
fn kernel_default_spawn_cwd_matches_workspace() {
412412
let captured_cwd = Arc::new(Mutex::new(None));
413413
let captured_cwd_for_permission = Arc::clone(&captured_cwd);
414414

@@ -437,7 +437,7 @@ fn kernel_default_spawn_cwd_matches_home_user() {
437437
.lock()
438438
.expect("captured cwd lock poisoned")
439439
.as_deref(),
440-
Some("/home/user")
440+
Some("/workspace")
441441
);
442442

443443
process.finish(0);

0 commit comments

Comments
 (0)