Skip to content

Tips and Tricks

Shiva Poudel edited this page Dec 26, 2018 · 14 revisions

How can I have different groups of tasks for different hosts with different configurations?

Simple setup

See here for information on using machine-specific configs.

More advanced setup

If you want to install programs independently from a general configuration file, the following setup might be for you.

Configurations

Write a configuration file for each program and put them together in a directory:

meta/configs/
├── bash.yaml
├── git.yaml
├── i3.yaml
└── ...

Then add a basic configuration file (i.e. for cleaning up) at meta/base.yaml.

Profiles

Then summarize these configurations in profiles:

meta/profiles/
├── server
├── workstation
└── ...

In a profile you specify the configurations you want to install (one per line, without .yaml).

New install scripts

Then replace the install script with the following ones:

install-profile
#!/usr/bin/env bash

set -e

BASE_CONFIG="base"
CONFIG_SUFFIX=".yaml"

META_DIR="meta"
CONFIG_DIR="configs"
PROFILES_DIR="profiles"

DOTBOT_DIR="dotbot"
DOTBOT_BIN="bin/dotbot"

BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

cd "${BASE_DIR}"
git submodule update --init --recursive --remote

while IFS= read -r config; do
	CONFIGS+=" ${config}"
done < "${META_DIR}/${PROFILES_DIR}/$1"

shift

echo ${CONFIGS}

for config in ${CONFIGS} ${@}; do
	echo -e "\nConfigure $config"
	configFile="$(mktemp)" ; echo -e "$(<"${BASE_DIR}/${META_DIR}/${BASE_CONFIG}${CONFIG_SUFFIX}")\n$(<"${BASE_DIR}/${META_DIR}/${CONFIG_DIR}/${config}${CONFIG_SUFFIX}")" > "$configFile"
	"${BASE_DIR}/${META_DIR}/${DOTBOT_DIR}/${DOTBOT_BIN}" -d "${BASE_DIR}" -c "$configFile" ; rm -f "$configFile"
done
install-standalone
#!/usr/bin/env bash

set -e

BASE_CONFIG="base"
CONFIG_SUFFIX=".yaml"

META_DIR="meta"
CONFIG_DIR="configs"
PROFILES_DIR="profiles"

DOTBOT_DIR="dotbot"
DOTBOT_BIN="bin/dotbot"

BASE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

cd "${BASE_DIR}"
git submodule update --init --recursive --remote

for config in ${@}; do
	configFile="$(mktemp)" ; echo -e "$(<"${BASE_DIR}/${META_DIR}/${BASE_CONFIG}${CONFIG_SUFFIX}")\n$(<"${BASE_DIR}/${META_DIR}/${CONFIG_DIR}/${config}${CONFIG_SUFFIX}")" > "$configFile"
	"${BASE_DIR}/${META_DIR}/${DOTBOT_DIR}/${DOTBOT_BIN}" -d "${BASE_DIR}" -c "$configFile" ; rm -f "$configFile"
done

Now you should be able to install a profile with

./install-profile <profile> [<configs...>]

and single configurations with

./install-standalone <configs...>

If you have any open questions or something is unclear, you can try to take a look at a dotfile repository that uses this setup:

Automatically install or update dotfiles when ssh'ing into a remote machine (or: let my dotfiles follow me)

Original inspiration: http://klaig.blogspot.co.at/2013/04/make-your-dotfiles-follow-you.html

In your local ~/.ssh/config:

Host some.remote.host.example.com
    PermitLocalCommand yes
    # Unfortunately ssh does not support line breaks in config files
    LocalCommand ssh -o PermitLocalCommand=no %n "which git >/dev/null && ([[ -d ~/dotfiles ]] && (echo "Updating dotfiles on %h ..." && cd ~/dotfiles && git pull -q && ./install >/dev/null) || (echo "Installing dotfiles on %h ..." && git clone -q https://github.com/MYNAMESPACE/dotfiles && ./dotfiles/install >/dev/null))"

Relevant part broken down for readability:

LocalCommand ssh -o PermitLocalCommand=no %n "which git >/dev/null && ([[ -d ~/dotfiles ]] && \
  (echo "Updating dotfiles on %h ..." && cd ~/dotfiles && git pull -q && ./install >/dev/null) || \
  (echo "Installing dotfiles on %h ..." && git clone -q https://github.com/MYNAMESPACE/dotfiles && ./dotfiles/install >/dev/null))"

The main attraction here is the clever use of LocalCommand:

LocalCommand Specifies a command to execute on the local machine after successfully connecting to the server.

We use LocalCommand to run a second SSH session to connect to the same remote machine and execute the defined command line in that SSH session. So what happens is this:

  1. Initiate SSH connection to remote machine (ssh user@some.remote.host.example.com)

  2. If the connection is successful (including authentication) then LocalCommand is executed

  3. The LocalCommand initiates a second SSH connection to the same remote machine and executes the command line specified. This commandline updates or installes the dotfiles.

    The second SSH connection sets -o PermitLocalCommand=no so that no local command is executed for that SSH connection. Without this setting each SSH connection would initiate another SSH connection, which would initiate another SSH connection, ad infinitum. Wo don't want that.

  4. When the command line of the second SSH connection is finished then the LocalCommand is finished

  5. The initial SSH session is finally established and the remote shell becomes available

The command line is in Bash syntax so adapt it if you use a different shell.

First we check if git is even available (if it is not then nothing more will happen). Then, if there is a dotfiles directory in the user's home we cd into it, run git pull and install, thus updating the local dotfiles repository and installing any new or changed files or symlinks.

If there isn't a dotfiles repository in the user's home we do a fresh installation by cloning the dotfiles repo from Github and running the install command.

Obviously this LocalCommand is executed every time you connect to the remote machine so it will take a few seconds before your remote shell becomes available.

This works best if you use public key authentication (or GSSAPI/Kerberos authentication) so SSH doesn't ask for a password when logging in. If you do use password authentication then you will need to enter your password once for each of the two SSH connections.

Automatically update your Dotbot config file when you add files in Git

You can use this tool (implemented as a Git pre-commit hook) to automatically update Dotbot's config file when adding files in Git: https://github.com/gwerbin/dotbot-autobot.

Uninstall script

Currently, dotbot does not support uninstalling the symlinks. This script can be a good starting point for users who want this feature (source)

#!/usr/bin/env python

from __future__ import print_function

import yaml
import os

CONFIG="install.conf.yaml"

stream = open(CONFIG, "r")
conf = yaml.load(stream)

for section in conf:
    if 'link' in section:
        for target in section['link']:
            realpath = os.path.expanduser(target)
            if os.path.islink(realpath):
                print("Removing ", realpath)
                os.unlink(realpath)
Clone this wiki locally
You can’t perform that action at this time.