chezmoi icon indicating copy to clipboard operation
chezmoi copied to clipboard

Remote install dotfiles over ssh without installing chezmoi on the machine

Open twpayne opened this issue 3 years ago • 5 comments

Is your feature request related to a problem? Please describe.

It would be nice if chezmoi could install my dotfiles on a machine that I can ssh into, without me having to install chezmoi on that machine. This would be very helpful for ephemeral virtual machines and dev containers, for example.

Describe the solution you'd like

I would like to able to run:

$ chezmoi apply --remote=machine.example.com

and have chezmoi install my dotfiles over SSH on machine.example.com without me having to install chezmoi on machine.example.com.

Describe alternatives you've considered

chezmoi already has much of the functionality to make this possible, it can roughly be simulated with:

$ chezmoi archive | ssh machine.example.com -- tar -xf -

However, chezmoi archive will generate dotfiles for the local machine, not the remote machine. This can be mitigated with

$ chezmoi --config=remote-chezmoi-config.toml archive | ssh machine.example.com -- tar -xf -

where remote-chezmoi-config.toml is a chezmoi configuration file for the remote machine. However, the use of any non-hermetic template variables, e.g. .chezmoi, or the use of non-hermetic template functions, e.g. env, output, or lookPath will still apply to the local machine, not the remote machine.

Possible ways around this include:

  • Requiring the user's templates to not use any non-hermetic template variables or functions. This is possible now.
  • Using a two-phase SSH: in the first phase a SSH connection is used to retrieve the value of .chezmoi from the remote machine, and in the second phase the archive is generated using the value of .chezmoi and extracted on the remote machine. Non-hermetic template functions are not likely to be supported.

Additional context

  • The remote install functionality should also work for local Docker containers.
  • One-shot installation of dotfiles is probably sufficient. There is no need to support diff for remotes.

twpayne avatar Sep 14 '21 18:09 twpayne

Another way around it:

ssh -t [email protected] 'sh <(curl -fsSL git.io/chezmoi) -- init --one-shot felipecrs'

The caveats are of course:

  1. chezmoi binary needs to be re-downloaded
  2. dotfiles repo needs to be re-downloaded

felipecrs avatar Oct 05 '21 22:10 felipecrs

Random drive-by thoughts:

  • go-git supports in memory storage so it should be possible to do this without touching disk.
  • Rather than creating an archive to be untar'd, the remote install should probably create a portable shell script to be run on the target machine, much like the old shar format.

twpayne avatar Jun 12 '22 21:06 twpayne

I believe it will be better if we can directly connect to a remote machine and execute the process remotely. It's not that hard, but it allows us to run hook scripts in a specific environment, and we can also use the local environment to gather additional information. An additional feature - we will not do unnecessary manual work, such as unpacking the archive and then copying the files.

But we also need to think about what we will do after applying the changes:

  • automatically install chezmoi on the remote server (because we might want to manage dotfiles on the remote machine)
  • manage remote dotfiles from the local machine only (without installing chezmoi on a remote machine)
  • hybrid system? in this case we can imagine that we have 10/100 machines. And what do we expect?

If you have answers for this questions and plan for how to implement this feature - I can implement this feature.

kazhuravlev avatar Mar 17 '23 01:03 kazhuravlev

This is a great idea!

In the meantime I extended your install script to allow installations over SSH.

When you run install_chezmoi local, it will install it locally in almost the same way your original script did it. But if you add a SSH host as the first parameter like install_chezmoi [email protected], then it will first scp itself to the remote host and then execute the script with the local parameter on it.

It works well for me.

#!/bin/sh

set -e # -e: exit on error

# Default repo
repo_default="[email protected]:your_user/dotfiles.git"

print_help() {
  echo "Usage: $0 <destination> [--repo <git-repo>]"
  echo
  echo "This script installs chezmoi either locally or on a remote host."
  echo
  echo "Arguments:"
  echo "  destination  If 'local', the script will install chezmoi locally. If anything else, it will"
  echo "               assume it's an SSH host and will install chezmoi on the remote host."
  echo
  echo "Options:"
  echo "  --repo       Specify a different git repository to use. Default is \"$repo_default\"."
  echo "  -h, --help   Show this help message."
}

install_local() {
  repo="$1"
  if [ ! "$(command -v chezmoi)" ]; then
    bin_dir="$HOME/.local/bin"
    chezmoi="$bin_dir/chezmoi"
    if [ "$(command -v curl)" ]; then
      sh -c "$(curl -fsSL https://git.io/chezmoi)" -- -b "$bin_dir"
    elif [ "$(command -v wget)" ]; then
      sh -c "$(wget -qO- https://git.io/chezmoi)" -- -b "$bin_dir"
    else
      echo "To install chezmoi, you must have curl or wget installed." >&2
      exit 1
    fi
  else
    chezmoi=chezmoi
  fi

  # exec: replace current process with chezmoi init
  exec "$chezmoi" init "$repo"
}

install_remote() {
  repo="$1"
  remote_host="$2"
  script_name="$(basename "$0")"
  scp "$0" "$remote_host:/tmp/$script_name"
  ssh -t "$remote_host" "chmod +x /tmp/$script_name; /tmp/$script_name local --repo $repo"
}

# Parse command-line options
repo="$repo_default"
while [ $# -gt 0 ]; do
  case "$1" in
    --repo)
      shift
      repo="$1"
      ;;
    -h|--help)
      print_help
      exit 0
      ;;
    *)
      # If it's neither --repo nor -h/--help, it must be the destination
      destination="$1"
      ;;
  esac
  shift
done

# If no destination was provided, print the help message
if [ -z "$destination" ]; then
  print_help
  exit 1
fi

if [ "$destination" = "local" ]; then
  install_local "$repo"
else
  install_remote "$repo" "$destination"
fi

infused-kim avatar Feb 02 '24 17:02 infused-kim