#+title: My personal environment sync #+description: #+keywords: programming #+author: Yann Esposito #+email: yann@esposito.host #+date: [2021-10-30 Sat] #+lang: en #+options: auto-id:t #+startup: showeverything I have a quite specific system that I improved along the years to manage my local environment. Think about, binaries I expect to have in my shell, as well as configuration files for various utilities, and a few personal scripts. The notion of what is exactly my local environment is not perfectly defined. I expect every of my computers to behave slightly differently. Some are for work-only, some personal use only. For the things I want everywhere, I have a peculiar personal system. I use a personal script that depends on [[https://yadm.io][yadm]] and [[https://github.com/nix-community/home-manager][home-manager]]. My script try to check if some files where updated and react accordingly: 1. I download the dot-files changes via =yadm=. 2. If my home-manager files changes, it will run ~home-manager switch~ ; if it fails, try to update nix channels then try again. 3. If my doom emacs packages changed, it will run ~doom sync~ 4. If the script itself changed, it re-run the script after updating itself. If the script detect that I changed my emacs configuration, it runs ~doom sync~ or ~doom sync -u~. Here it is: #+begin_src bash #!/bin/bash ### logs fn helpers ## colors for tput # black=0 red=1 green=2 yellow=3 blue=4 # magenta=5 # cyan=6 # white=7 highpr() { printf "$(tput setaf $green)→$(tput sgr0) $(tput bold)%-60s$(tput sgr0)" "$*" } ok() { local txt="OK" echo -e " [$(tput bold)$(tput setaf $green)${txt}$(tput sgr0)]" >&2 } info() { echo -e " [$(tput bold)$(tput setaf $blue)$*$(tput sgr0)]" >&2 } warn() { echo -e "$(tput bold)$(tput setaf $yellow)$*$(tput sgr0)" >&2 } err() { echo -e "$(tput bold)$(tput setaf $red)$*$(tput sgr0)" >&2 } fail() { err -e "\n[ERR] $*" exit 1 } highpr "check nix" if ! [ -x "$(command -v nix)" ]; then echo err "nix does not seem to be installed." err "Install it from: https://nixos.org/nix/" exit 1 fi ok highpr "yadm fetch" yadm fetch --quiet || fail "yadm fetch failed" ok # check the hash of a few files before doing yadm pull OLD_SYNC_ENV_ID=$(yadm rev-parse HEAD:bin/sync-env.sh) OLD_HOME_MANAGER_ID=$(yadm rev-parse HEAD:.config/nixpkgs/home.nix) OLD_DOOM_PACKAGES=$(yadm rev-parse HEAD:.doom.d/packages.el) OLD_DOOM_INIT=$(yadm rev-parse HEAD:.doom.d/init.el) highpr "yadm pull" yadm pull --quiet || fail "yadm pull failed" ok # check the hash of a few files after doing yadm pull NEW_SYNC_ENV_ID=$(yadm rev-parse HEAD:bin/sync-env.sh) NEW_HOME_MANAGER_ID=$(yadm rev-parse HEAD:.config/nixpkgs/home.nix) NEW_DOOM_PACKAGES=$(yadm rev-parse HEAD:.doom.d/packages.el) NEW_DOOM_INIT=$(yadm rev-parse HEAD:.doom.d/init.el) highpr "check sync-env diff" if ! [ "$OLD_SYNC_ENV_ID" = "$NEW_SYNC_ENV_ID" ]; then warn " changed" warn " Starting ~/bin/sync-env.sh again" echo ~/bin/sync-env.sh exit $? fi ok if [ -f "$HOME/.yadm/files.gpg" ]; then highpr "yadm decrypt" yadm decrypt || fail "yadm decrypt failed" ok fi highpr "home-manager" USERNAME_NIX_FILE="$HOME/.config/nixpkgs/username.nix" if [ ! -f "$USERNAME_NIX_FILE" ]; then echo "\"$USER\"" >> "$USERNAME_NIX_FILE" fi if ! [ "$OLD_HOME_MANAGER_ID" = "$NEW_HOME_MANAGER_ID" ]; then echo highpr "home-manager switch" home-manager switch || \ ( nix-channel --update && home-manager switch ) || \ fail "home-manager switch failed" ok else info "skipped" fi highpr "doom-emacs" doompath="$HOME/.emacs.d/bin/doom" if ! [ "$OLD_DOOM_PACKAGES" = "$NEW_DOOM_PACKAGES" ] || \ ! [ "$OLD_DOOM_INIT" = "$NEW_DOOM_INIT" ]; then if [[ -x $doompath ]]; then echo highpr "doom sync" $doompath sync || fail "doom failed to sync" ok else fail "Cannot find doom executable at $doompath"; fi else info "skipped" fi #+end_src ** Bootstrapping :PROPERTIES: :CUSTOM_ID: boostraping :END: Bootstrapping this system is always a nice problem to think about. It is smooth when everything is set but to bootstrap it I need binaries installed by this system... So... How to handle the dependency cycle correctly? To minimize the pain, I removed more and more bootstrapping dependencies. Now my almost single dependence for bootstrapping my environment is =nix=. I haven't initialized any machine for a long time now. The following should work. 0. Use fish[fn:fish] ~chsh /bin/fish~ 1. Install nix ~curl -L https://nixos.org/nix/install | sh~ 2. Install home-manager #+begin_src bash nix-channel --add https://github.com/nix-community/home-manager/archive/release-21.05.tar.gz home-manager nix-channel --update export NIX_PATH=$HOME/.nix-defexpr/channels${NIX_PATH:+:}$NIX_PATH nix-shell '' -A install #+end_src 3. Install and use ~yadm~ #+begin_src bash nix-shell -p yadm yadm boostrap yadm remote set-url origin yadm pull #+end_src 4. Still in the =nix-shell= with =yadm= run ~~/bin/sync-env.sh~ There is a risk that step 3 fail because I pin most of my packages in home-manager configuration, and it will try to install =yadm=. This can conflict with the =yadm= installed in the current =nix-shell=. So sometime I need to: 1. Remove the line installing =yadm= in my home-manager configuration first 2. run =home-manager sync= 3. get out of the =nix-shell=, 4. add =yadm= back in the =home-manager= config 5. run =home-manager sync= again, but this time out of the =nix-shell=. 6. Finally I can run my =~/bin/sync-env.sh= command. So this post will probably be useful as a personal note in the future. Because bootstrapping is generally not trivial. I will probably update this post if something is missing. [fn:fish] I use fish for interactive shell. I use ~zsh~ for quick dirty scripts (a lot better than bash), and I switch to [[https://hackage.haskell.org/package/turtle][turtle]] if I need to be serious about the script.