Skip to content

RFC: Auto-provision Nix packages on remote machines #3

@andrewgazelka

Description

@andrewgazelka

Summary

Add the ability to specify Nix packages in zsync.toml that get automatically provisioned on the remote machine, enabling consistent development environments without requiring root access on the remote.

Motivation

When syncing to remote development machines (especially cloud GPUs, HPC clusters, or shared servers), you often want specific tools available (neovim, ripgrep, fd, etc.) but:

  • Don't have root access to install packages
  • Don't want to manually set up each machine
  • Want reproducible environments across different remotes

Research: Rootless Nix Options

1. nix-portable (Recommended)

Repo: https://github.com/DavHau/nix-portable

Pros:

  • Single static binary (~100MB), no installation needed
  • Works on any Linux without root or configuration
  • Supports x86_64 and aarch64
  • Uses bubblewrap (fast, user namespaces) or falls back to proot
  • Flakes and nix-command enabled by default
  • MIT licensed, actively maintained (1.1k stars)

Cons:

  • proot fallback has significant performance overhead
  • Programs from nix-portable can't link against system programs
  • macOS not supported (but zsync remote is always Linux)

How it works:

  • Self-extracting archive → $HOME/.nix-portable/
  • Virtualizes /nix/store using bubblewrap or proot
  • Runtime selection: nix → bubblewrap → proot (in preference order)

2. nix-user-chroot

Repo: https://github.com/nix-community/nix-user-chroot

Pros:

  • Enables nix sandbox (better isolation)
  • Auto-mounts OpenGL/CUDA libraries (good for GPU machines)
  • Rust-based, static binary available

Cons:

  • Currently unmaintained
  • Requires user namespaces (disabled on RHEL/CentOS by default)
  • More complex setup than nix-portable

3. Native Nix rootless install

Newer Nix versions support --no-daemon single-user install, but still typically needs write access to create /nix or use of user namespaces.

Recommendation: nix-portable

Best balance of simplicity, compatibility, and maintenance status. Falls back gracefully when user namespaces aren't available.

Proposed Design

Config (zsync.toml)

[nix]
# Packages to provision on the remote
packages = [
  "neovim",
  "ripgrep", 
  "fd",
  "jq",
  "gh",
]

# Optional: use a flake instead of packages
# flake = "github:user/devenv"

# Auto-add nix packages to PATH (default: true)
setup_path = true

Behavior

  1. On first sync to a new remote:

    • Check if nix/nix-portable is available
    • If not, download nix-portable binary to ~/.zsync/nix-portable
    • Run nix-portable nix profile install nixpkgs#<package> for each package
  2. On subsequent syncs:

    • Check if packages are installed, install missing ones
    • Optionally update packages (nix profile upgrade)
  3. PATH setup:

    • Add ~/.nix-profile/bin to shell rc files
    • Or source a zsync-generated env script

Implementation Notes

  • nix-portable binary is ~100MB, cache it on remote
  • Package installation happens async/parallel with file sync
  • Could show progress: Provisioning neovim, ripgrep...
  • Store package state in ~/.zsync/nix-state.json to avoid re-checking

Open Questions

  1. Flake support? Allow specifying a flake for more complex environments?
  2. Shell integration? Auto-add to .bashrc/.zshrc or require manual sourcing?
  3. Update policy? Auto-update packages? On every sync? Manual only?
  4. Binary cache? Use cachix or other binary caches for faster installs?

Alternatives Considered

  • Just document manual nix-portable setup - Works but loses the "it just works" UX
  • Embed nix-portable in zsync-agent - Too large, not all users need it
  • Use system package managers - Not portable, requires root

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions