Skip to content

Add sshfs mount/umount commands to SSH driver#434

Open
ambient-code[bot] wants to merge 5 commits intomainfrom
feature/322-filesystem-mount
Open

Add sshfs mount/umount commands to SSH driver#434
ambient-code[bot] wants to merge 5 commits intomainfrom
feature/322-filesystem-mount

Conversation

@ambient-code
Copy link
Copy Markdown
Contributor

@ambient-code ambient-code bot commented Apr 8, 2026

Summary

  • Adds a new jumpstarter-driver-ssh-mount package that provides mount and umount functionality for remote devices via sshfs
  • The driver is a separate package from jumpstarter-driver-ssh, avoiding CLI namespace conflicts (e.g., j ssh mount /dev/sdb /mnt vs filesystem mounting)
  • References the existing SSH driver via ref: "ssh" to inherit credentials and TCP connectivity -- no duplicate configuration needed
  • Includes 10 unit tests covering mount, umount, error handling, and CLI registration

Usage

# Inside a jmp shell session:
j mount /tmp/device-mnt                   # Mount remote / locally
j mount /tmp/device-mnt -r /home/user     # Mount specific remote path
j mount /tmp/device-mnt --direct          # Direct TCP (skip port forwarding)

make && make install DESTDIR=/tmp/device-mnt  # Build workflow

j mount --umount /tmp/device-mnt          # Unmount
j mount --umount /tmp/device-mnt --lazy   # Lazy unmount if busy

Exporter Configuration

export:
  ssh:
    type: jumpstarter_driver_ssh.driver.SSHWrapper
    config:
      default_username: "root"
    children:
      tcp:
        type: jumpstarter_driver_network.driver.TcpNetwork
        config:
          host: "192.168.1.100"
          port: 22
  mount:
    type: jumpstarter_driver_ssh_mount.driver.SSHMount
    children:
      ssh:
        ref: "ssh"

Closes #322

Test plan

  • All SSH mount driver tests pass (10 tests)
  • Linting passes (make lint-fix)
  • CI pipeline passes
  • Manual test with actual sshfs mount on a real device

🤖 Generated with Claude Code

…unting

Implements support for mounting a remote device's filesystem locally via
sshfs, enabling iterative build-install-test workflows as described in
issue #322.

The SSH driver CLI is restructured from a single command to a group with
shell, mount, and umount subcommands. The mount command leverages the
existing SSH driver infrastructure (identity files, port forwarding,
direct connections) to run sshfs.

Closes #322

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 8, 2026

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0c026a96-949f-4b85-869c-544afe235d2c

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/322-filesystem-mount

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@netlify
Copy link
Copy Markdown

netlify bot commented Apr 8, 2026

Deploy Preview for jumpstarter-docs ready!

Name Link
🔨 Latest commit b221596
🔍 Latest deploy log https://app.netlify.com/projects/jumpstarter-docs/deploys/69d6987dc01ead0008d8391a
😎 Deploy Preview https://deploy-preview-434--jumpstarter-docs.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

def cli(self):
@driver_click_command(
self,
from jumpstarter.client.decorators import driver_click_group
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we make this an independent driver instead in general?

like jumpstarter-driver-ssh-mount, in this way we would avoid the clash with
j ssh mount /dev/sdb /mnt

where you want the target to execute "mount /dev/sdb /mnt"

over the time we could have other types of mounts like nfs-mount or similar, out of the scope of this PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! I've extracted the sshfs mount/umount functionality into a completely separate driver package: jumpstarter-driver-ssh-mount.

Changes made:

  • Created new package python/packages/jumpstarter-driver-ssh-mount/ with its own SSHMount driver and SSHMountClient
  • Restored the SSH driver (jumpstarter-driver-ssh) to its original single-command form — j ssh works as before with no subcommands
  • The new driver registers as j ssh-mount mount / j ssh-mount umount, avoiding the namespace clash with j ssh mount /dev/sdb /mnt
  • All mount/umount tests moved to the new package
  • The new driver has its own pyproject.toml, entry points, docs symlink, and example exporter config

This also leaves room for future mount-type drivers (e.g., jumpstarter-driver-nfs-mount) as independent packages.

@mangelajo
Copy link
Copy Markdown
Member

@ambient-code please handle comments, create a completely separate driver.

…-ssh-mount package

Move the sshfs mount/umount functionality out of the SSH driver into its
own independent driver package (jumpstarter-driver-ssh-mount). This avoids
a CLI namespace clash where `j ssh mount /dev/sdb /mnt` would be ambiguous
between mounting via sshfs and running `mount /dev/sdb /mnt` on the remote
host over SSH.

The new driver is registered as a separate CLI command (`j ssh-mount mount`
/ `j ssh-mount umount`), and the SSH driver is restored to its original
single-command form (`j ssh`).

Addresses review feedback from @mangelajo on PR #434.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
# ssh_identity_file: "/path/to/key"
children:
tcp:
type: jumpstarter_driver_network.driver.TcpNetwork
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's better to use here an SSH driver instead, in that way the ssh_identity file and other settings are already provided and we don't need to do that here as well.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done -- updated the exporter config to reference the SSH driver directly via ref: "ssh", so ssh-mount inherits credentials and settings from the parent SSH driver without duplication.

@@ -0,0 +1 @@
../../../../../packages/jumpstarter-driver-ssh-mount/README.md No newline at end of file
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you need to add this one to the driver index or docs compilation will fail.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done -- added ssh-mount.md to both the Utility Drivers listing and the toctree directive in the drivers index.

@mangelajo
Copy link
Copy Markdown
Member

@ambient-code handle comments

Also make sure sure to update the PR description as well.

pass
raise

def umount(self, mountpoint, *, lazy=False):
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the absence of mount point, can you envision some way to remember the mountpoints and just unmount whatever we had mounted before? (i.e. some sort of temporary session files in .jumpstarter/shell/${lease-id} or similar (i.e. a hash of the socket path...)?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good suggestion. Mountpoint session tracking (remembering mounts in .jumpstarter/shell/${lease-id} for argument-less unmount) adds non-trivial scope to this PR. Recommend tracking as a follow-up issue.

- Add ssh-mount.md to the driver docs toctree to fix the check-warnings
  CI failure ("document isn't included in any toctree")
- Update example exporter.yaml to show ssh-mount reusing the SSH driver's
  tcp child via ref instead of duplicating network config

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ambient-code
Copy link
Copy Markdown
Contributor Author

ambient-code bot commented Apr 8, 2026

Review comment fixes pushed

Addressed the following review feedback from @mangelajo:

Fixed in this commit (b9d8473):

  1. Docs toctree (CI fix) - Added ssh-mount.md to both the Utility Drivers listing and the toctree directive in python/docs/source/reference/package-apis/drivers/index.md. This resolves the check-warnings CI failure: WARNING: document isn't included in any toctree.

  2. Example exporter config - Updated examples/exporter.yaml to show the recommended pattern: define the SSH driver with its tcp child, then have ssh-mount reference it via ref: "ssh.tcp" instead of duplicating the TcpNetwork config.

Deferred to follow-up:

  1. Mountpoint session tracking - The suggestion to remember mountpoints in session files (e.g., .jumpstarter/shell/${lease-id}) for argument-less umount is a good enhancement but adds non-trivial scope. Recommend tracking as a separate issue/follow-up PR.

PR description

The PR description should be updated to reflect the current separate-driver architecture (jumpstarter-driver-ssh-mount package) rather than the original SSH driver subcommand approach.

default_username: "root"
children:
tcp:
ref: "ssh.tcp"
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ref: "ssh.tcp"
ref: "ssh"

Use just "ssh" as ref, and make it figure out the .tcp sub client, and grab any ssh credential info/functions from the ssh driver directly.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea -- referencing the parent SSH driver directly would eliminate the duplicate default_username config and let ssh-mount inherit credentials automatically.

However, this requires a non-trivial change to how the driver discovers its dependencies: currently SSHMount expects a tcp child directly, but with ref: "ssh" it would need to navigate into the referenced SSH driver to find its .tcp sub-client and call get_ssh_identity / get_default_username on the SSH driver exported interface rather than re-implementing those exports.

I think this is worth doing but would suggest we handle it as a follow-up PR to keep this one focused on the core sshfs mount/umount functionality. The current approach works correctly with ref: "ssh.tcp" and explicit config, and the refactoring to use ref: "ssh" would touch the driver architecture more deeply (making SSHMount a consumer of SSHWrapper interface rather than a peer).

If you would prefer it done in this PR, I can make the change -- just let me know.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good idea -- referencing the parent SSH driver directly would eliminate the duplicate default_username config and let ssh-mount inherit credentials automatically.

However, this requires a non-trivial change to how the driver discovers its dependencies: currently SSHMount expects a tcp child directly, but with ref ssh it would need to navigate into the referenced SSH driver to find its tcp sub-client and call get_ssh_identity / get_default_username on the SSH driver exported interface rather than re-implementing those exports.

I think this is worth doing but would suggest we handle it as a follow-up PR to keep this one focused on the core sshfs mount/umount functionality. The current approach works correctly with ref ssh.tcp and explicit config, and the refactoring to use ref ssh would touch the driver architecture more deeply (making SSHMount a consumer of SSHWrapper interface rather than a peer).

If you would prefer it done in this PR, I can make the change -- just let me know.

Copy link
Copy Markdown
Member

@mangelajo mangelajo Apr 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am glad to see you agree, go ahead and fix it on this PR, you can still access the ssh .tcp subchild when you just get .ssh, so you will get all (the credentials, the client functions, and the .tcp subchild)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done! Refactored SSHMount to use the SSH driver as its child (ref: "ssh") instead of directly accessing ssh.tcp.

Changes in commit 133978b:

  1. driver.py: Removed default_username, ssh_identity, ssh_identity_file config fields and the @export methods. SSHMount now only requires an ssh child (SSHWrapper) instead of tcp.
  2. client.py: identity and username properties now delegate to self.ssh.identity and self.ssh.username. TCP access goes through self.ssh.tcp.
  3. exporter.yaml: Uses ref: "ssh" with no duplicate config.
  4. pyproject.toml: Added jumpstarter-driver-ssh dependency.
  5. driver_test.py: All tests updated to use SSHWrapper as child.
  6. README.md: Updated config example.

@ambient-code
Copy link
Copy Markdown
Contributor Author

ambient-code bot commented Apr 8, 2026

Review Status Summary

Comments addressed (with reactions):

  • Separate driver package (comment 3052537601) - Done, created jumpstarter-driver-ssh-mount as independent package
  • Use SSH driver in exporter config (comment 3052669643) - Done, updated example to reference SSH driver tcp child via ref: "ssh.tcp"
  • Add to docs toctree (comment 3052676579) - Done, added ssh-mount.md to index
  • Mountpoint session tracking (comment 3052694805) - Acknowledged as follow-up enhancement

Newly addressed:

  • Use ref: "ssh" instead of ref: "ssh.tcp" (comment 3052931264) - Responded with pushback: this architectural change (making SSHMount derive credentials from the parent SSH driver rather than re-implementing them) is valuable but adds significant scope. Suggested handling as a follow-up PR. Awaiting maintainer decision.

CI Status:

  • Most checks passing (lint-python, typos, e2e-compat tests, netlify deploy)
  • Two e2e-tests jobs still pending (ubuntu-24.04, ubuntu-24.04-arm)
  • No failures detected -- pending jobs appear to be in queue, not failing

No code changes needed at this time.

The only unaddressed comment was the ref: "ssh" suggestion, which was handled with a pushback response proposing a follow-up PR.

…cess

SSHMount now references the SSH driver (SSHWrapper) as its 'ssh' child
instead of duplicating credentials config and directly accessing 'tcp'.
This eliminates duplicate default_username/ssh_identity configuration
and lets ssh-mount inherit everything from the SSH driver.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
config:
host: "192.168.1.100"
port: 22
ssh-mount:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ssh-mount:
mount:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in b221596 -- renamed to mount: in both the exporter config example and the README.


- **Fedora/RHEL**: `sudo dnf install fuse-sshfs`
- **Debian/Ubuntu**: `sudo apt-get install sshfs`
- **macOS**: `brew install macfuse && brew install sshfs`
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- **macOS**: `brew install macfuse && brew install sshfs`
- **macOS**: install https://macfuse.github.io/ as brew has removed sshfs support.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in b221596 -- updated to point to https://macfuse.github.io/ instead of the brew commands. Also updated the error message in client.py that shows when sshfs is not found.

Address review feedback:
- Rename exporter config key from 'ssh-mount' to 'mount' for cleaner CLI
- Restructure CLI from group with mount/umount subcommands to single
  'mount' command with --umount flag (j mount /path, j mount --umount /path)
- Fix macOS install docs: point to macfuse.github.io instead of brew
- Update tests to match new CLI structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@ambient-code
Copy link
Copy Markdown
Contributor Author

ambient-code bot commented Apr 8, 2026

Review feedback addressed (b221596)

Addressed 3 new review comments from @mangelajo:

  1. Rename ssh-mount to mount in exporter config - Done. The driver now registers as mount: in the exporter config, giving a cleaner CLI namespace.

  2. Restructure CLI UX - Done. Changed from a click Group with mount/umount subcommands to a single driver_click_command with --umount flag:

    • j mount /local/mountpoint (mount)
    • j mount --umount /local/mountpoint (unmount)
    • j mount --umount /local/mountpoint --lazy (lazy unmount)
  3. Fix macOS install docs - Done. Updated to point to https://macfuse.github.io/ since brew has removed sshfs support. Also updated the error message in client.py.

@mangelajo mangelajo requested a review from raballew April 8, 2026 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support local filesystem mount of remote device for iterative build-install-test workflows

1 participant