Back to feed

sledtools/pika branch #84

pika-orch-incus-cleanup-17

Simplify Incus staged payload mounts

Target branch: master

Merge Commit: 38d699df899155d4479d5e61d8e7df1138a50196

branch: merged tutorial: ready ci: success
Open CI Details

Continuous Integration

CI: success

Compact status on the review page, with full logs on the CI page.

Open CI Details

Latest run #109 success

6 passed

head 7d0c875b564704c535f9c0e47ac96fdb2adcd5d3 · queued 2026-03-26 00:53:19 · 6 lane(s)

queued 6s · ran 34s

check-notifications · success check-agent-contracts · success check-pikachat · success check-pikachat-typescript · success check-pikachat-openclaw-e2e · success check-fixture · success

Summary

This branch simplifies how Incus staged payload mounts are resolved and attached to container instances. Previously, the orchestrator used a two-phase approach: ensure_remote_linux_vm_directories conditionally created staged output directories only when symlinks did not already exist, and prepare_remote_linux_vm_runtime resolved local paths and installed symlinks via a dedicated remote_symlink helper before delegating to the Incus backend. The new design removes the symlink indirection entirely, unconditionally creates all directories with a single mkdir -p invocation, and pushes the local-vs-remote source resolution into a new staged_payload_source_root function inside the Incus module. The HostContext is now threaded through prepare_runtime and configure_remote_incus_devices so the Incus backend can resolve localhost mounts itself, eliminating the intermediate remote_symlink function and the conditional shell logic.

Tutorial Steps

Simplify remote directory creation to unconditional mkdir -p

Intent: Replace the conditional shell logic that checked for existing symlinks before creating staged output directories with a single unconditional `mkdir -p` call covering all eight directories. This removes the `if [ ! -e ... ] && [ ! -L ... ]` guards that previously protected symlinked staged payload paths, since symlinks are no longer used.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -1923,12 +1923,7 @@ fn ensure_remote_linux_vm_directories(
-        concat!(
-            "set -euo pipefail; ",
-            "mkdir -p {} {} {} {} {} {}; ",
-            "if [ ! -e {} ] && [ ! -L {} ]; then mkdir -p {}; fi; ",
-            "if [ ! -e {} ] && [ ! -L {} ]; then mkdir -p {}; fi"
-        ),
+        "set -euo pipefail; mkdir -p {} {} {} {} {} {} {} {}",
@@ -1936,10 +1931,6 @@ fn ensure_remote_linux_vm_directories(
-        shell_single_quote(&shared.remote_workspace_deps_dir.display().to_string()),
-        shell_single_quote(&shared.remote_workspace_deps_dir.display().to_string()),
-        shell_single_quote(&shared.remote_workspace_build_dir.display().to_string()),
-        shell_single_quote(&shared.remote_workspace_build_dir.display().to_string()),

The old ensure_remote_linux_vm_directories built a shell command with a concat! macro that assembled three clauses:

  1. mkdir -p for the six core directories.
  2. A conditional if [ ! -e ... ] && [ ! -L ... ]; then mkdir -p ...; fi for workspace-deps.
  3. The same conditional for workspace-build.

The conditionals existed because a prior step could install symlinks at those paths (pointing to local staged payload directories on localhost). Creating the directory over an existing symlink would have been an error.

With symlinks removed from the workflow, these guards are unnecessary. The format string collapses to a single mkdir -p with eight positional arguments, and the duplicated shell_single_quote calls for the conditional branches are deleted. The two staged output paths (remote_workspace_deps_dir, remote_workspace_build_dir) are now created unconditionally alongside every other directory.

Remove the remote_symlink helper and the symlink-installation phase from prepare_remote_linux_vm_runtime

Intent: Delete the `remote_symlink` utility function and the block in `prepare_remote_linux_vm_runtime` that resolved local staged payload directories and installed symlinks on localhost. The local-vs-remote resolution responsibility is moved into the Incus backend, so this orchestrator-level indirection is no longer needed.

Affected files: crates/pikaci/src/executor.rs

Evidence
@@ -2238,70 +2229,14 @@ fn build_sync_directory_finalize_command(
-fn remote_symlink(
-    target: &Path,
-    link: &Path,
-    remote_host: &str,
-    log_path: &Path,
-) -> anyhow::Result<()> {
-    let shared = remote.shared();
-    if ssh_host_is_local(&shared.remote_host) {
-        if let Some(local_workspace_deps) = &ctx.staged_linux_rust_workspace_deps_dir {
-            ...
-            remote_symlink(
-                &realized,
-                &shared.remote_workspace_deps_dir,
-                &shared.remote_host,
-                log_path,
-            )?;
-        }
-        RemoteLinuxVmContext::Incus(remote) => incus::prepare_runtime(job, remote, log_path),
+        RemoteLinuxVmContext::Incus(remote) => incus::prepare_runtime(job, ctx, remote, log_path),

Two major deletions happen here:

  1. remote_symlink — a 15-line helper that ran mkdir -p <parent>; ln -sfn <target> <link> over SSH. It was only called from the localhost staged-payload block that is also being removed.

  2. The localhost symlink-installation block inside prepare_remote_linux_vm_runtime — this block checked ssh_host_is_local, resolved each local staged payload path with fs::canonicalize, and called remote_symlink to create symlinks at the remote workspace deps/build directories. All of this logic is replaced by passing ctx: &HostContext through to incus::prepare_runtime, letting the Incus module handle source resolution at device-configuration time.

The prepare_remote_linux_vm_runtime function body reduces to a single match expression that forwards ctx into the backend.

Thread HostContext through the Incus runtime and device configuration functions

Intent: Pass the `HostContext` reference into `prepare_runtime`, `ensure_remote_incus_runtime`, and `configure_remote_incus_devices` so the Incus backend has access to the local staged payload paths when configuring container disk devices.

Affected files: crates/pikaci/src/executor/incus.rs

Evidence
@@ -287,6 +287,7 @@ pub(super) fn load_image_record(
 pub(super) fn ensure_remote_incus_runtime(
     job: &JobSpec,
+    ctx: &HostContext,
     remote: &RemoteIncusContext,
@@ -342,10 +343,11 @@ pub(super) fn ensure_remote_incus_runtime(
 pub(super) fn prepare_runtime(
     job: &JobSpec,
+    ctx: &HostContext,
     remote: &RemoteIncusContext,
-    configure_remote_incus_devices(job, remote, &runtime_plan, log_path)?;
+    configure_remote_incus_devices(job, ctx, remote, &runtime_plan, log_path)?;

Three function signatures gain a ctx: &HostContext parameter:

  • prepare_runtime — the public entry point from executor.rs.
  • ensure_remote_incus_runtime — creates the Incus instance and configures devices.
  • configure_remote_incus_devices — the function that actually adds disk devices to the container.

This threading is mechanical: each caller passes ctx through unchanged. The important consumer is configure_remote_incus_devices, which will use ctx.staged_linux_rust_workspace_deps_dir and ctx.staged_linux_rust_workspace_build_dir to resolve the correct source path for each device mount.

Introduce staged_payload_source_root to resolve localhost vs. remote mount sources

Intent: Add a new function that centralizes the logic for deciding whether to use a local (canonicalized) path or the remote output path as the source root for a staged payload Incus disk device, depending on whether the remote host is localhost.

Affected files: crates/pikaci/src/executor/incus.rs

Evidence
@@ -797,8 +799,40 @@ fn add_declared_payload_mount(
+fn staged_payload_source_root(
+    local_mount_path: Option<&PathBuf>,
+    remote_output_root: &Path,
+    remote_host: &str,
+) -> anyhow::Result<PathBuf> {
+    if ssh_host_is_local(remote_host) {
+        let local_mount_path = local_mount_path.ok_or_else(|| {
+            anyhow!(
+                "missing local staged payload mount for localhost source {}",
+                remote_output_root.display()
+            )
+        })?;
+        return fs::canonicalize(local_mount_path).with_context(|| {
+            format!(
+                "resolve local staged payload source {}",
+                local_mount_path.display()
+            )
+        });
+    }
+    Ok(remote_output_root.to_path_buf())
+}

staged_payload_source_root encapsulates the localhost detection that was previously done in prepare_remote_linux_vm_runtime:

  • Localhost path: When ssh_host_is_local(remote_host) is true, the function requires a local_mount_path (from HostContext) and canonicalizes it with fs::canonicalize. If the local path is None, it returns an explicit error instead of silently skipping (a strictness improvement over the old code which used if let Some(...)).
  • Remote path: For non-localhost hosts, it returns remote_output_root as-is, since the path is already on the remote machine's filesystem.

This function is private to the Incus module, keeping the resolution logic co-located with the device configuration that consumes it.

Use resolved source roots when configuring staged payload Incus disk devices

Intent: Replace direct use of remote workspace directories as Incus disk device sources with the resolved paths from `staged_payload_source_root`, so localhost builds mount the actual local directory rather than relying on a symlink.

Affected files: crates/pikaci/src/executor/incus.rs

Evidence
@@ -808,18 +842,18 @@ fn configure_remote_incus_devices(
     if job.staged_linux_rust_lane().is_some() {
-        add_declared_payload_mounts(
-            remote,
+        let workspace_deps_root = staged_payload_source_root(
+            ctx.staged_linux_rust_workspace_deps_dir.as_ref(),
             &remote.shared.remote_workspace_deps_dir,
-            "workspace-deps",
-            log_path,
+            &remote.shared.remote_host,
         )?;
-        add_declared_payload_mounts(
-            remote,
+        add_declared_payload_mounts(remote, &workspace_deps_root, "workspace-deps", log_path)?;
+        let workspace_build_root = staged_payload_source_root(
+            ctx.staged_linux_rust_workspace_build_dir.as_ref(),
             &remote.shared.remote_workspace_build_dir,
-            "workspace-build",
-            log_path,
+            &remote.shared.remote_host,
         )?;
+        add_declared_payload_mounts(remote, &workspace_build_root, "workspace-build", log_path)?;

Inside configure_remote_incus_devices, the staged payload mount block now:

  1. Calls staged_payload_source_root for workspace-deps, passing ctx.staged_linux_rust_workspace_deps_dir as the optional local path.
  2. Passes the resolved workspace_deps_root to add_declared_payload_mounts.
  3. Repeats the same pattern for workspace-build.

Previously, add_declared_payload_mounts received &remote.shared.remote_workspace_deps_dir directly, which on localhost was expected to be a symlink pointing to the real local directory. Now the real path is computed up front and handed to the mount helper, eliminating the symlink dependency entirely.

Add test-only accessor and update unit tests for the new behavior

Intent: Expose `staged_payload_source_root` for testing via a `#[cfg(test)]` wrapper, rename the existing directory-creation test to reflect the new unconditional semantics, and add two new tests validating localhost canonicalization and remote passthrough behavior.

Affected files: crates/pikaci/src/executor.rs, crates/pikaci/src/executor/incus.rs

Evidence
@@ +0,0 @@
+#[cfg(test)]
+pub(super) fn resolve_staged_payload_source_root_for_test(
+    local_mount_path: Option<&PathBuf>,
+    remote_output_root: &Path,
+    remote_host: &str,
+) -> anyhow::Result<PathBuf> {
+    staged_payload_source_root(local_mount_path, remote_output_root, remote_host)
+}
@@ -3663,7 +3599,7 @@ mod tests {
-    fn ensure_remote_linux_vm_directories_skips_existing_staged_output_symlinks() {
+    fn ensure_remote_linux_vm_directories_create_declared_staged_output_dirs() {
@@ +3631,0 +3631,50 @@
+    #[test]
+    fn remote_linux_incus_uses_local_staged_payload_mount_for_localhost() {
+    #[test]
+    fn remote_linux_incus_preserves_remote_staged_payload_root_for_non_localhost() {

Testing changes cover three areas:

  1. resolve_staged_payload_source_root_for_test (incus.rs) — a #[cfg(test)] pub(super) wrapper that lets tests in the parent module call the private staged_payload_source_root without exposing it in production builds.

  2. Renamed testensure_remote_linux_vm_directories_skips_existing_staged_output_symlinks becomes ensure_remote_linux_vm_directories_create_declared_staged_output_dirs. The assertions are updated: the test now verifies that both staged paths appear in the mkdir -p argument list and that no if [ ! -e conditional remains in the command string.

  3. Two new tests:

    • remote_linux_incus_uses_local_staged_payload_mount_for_localhost — creates a temporary directory, calls resolve_staged_payload_source_root_for_test with remote_host = "localhost", and asserts the result equals the canonicalized local path.
    • remote_linux_incus_preserves_remote_staged_payload_root_for_non_localhost — calls with remote_host = "pika-build" and local_mount_path = None, asserts the remote path is returned unchanged.

The use chrono::Utc import is added to the test module to support unique temp directory naming via timestamp_nanos_opt.

Diff