Last active
April 5, 2017 07:48
-
-
Save arrdem/c748b942e14023f5469c1f6ed4bbe338 to your computer and use it in GitHub Desktop.
A bootleg puppet
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# -*- mode: bash; indent-tabs-mode: nil; sh-basic-offset: 2; fill-column: 100; -*- | |
# About | |
# ============================== | |
# A bootleg dotfiles installer. | |
# | |
# Usage | |
# ============================== | |
# | |
# $FORCE - if non-empty then install.sh will happily clobber existing files/dirs | |
# $DEBUG - if non-empty then install.sh will be verbose about what gets moved where | |
# | |
# $ bash ./install.sh | |
# | |
# Expects a repository of the following configuration: | |
# $ROOT/ | |
# $ROOT/install.sh | |
# $ROOT/profiles.d | |
# $ROOT/packages.d | |
# $ROOT/hosts.d | |
# | |
# packages.d is expected to be a directory of packages. | |
# profiles.d and hosts.d are expected to be directories of profiles. | |
# | |
# Packages | |
# -------------------- | |
# | |
# Packages consist of one or more directories of files to be installed, by default to ~/ | |
# Packages may provide a README.*, an install and a build which are ignored | |
# | |
# If a build file is present, the build will be executed before the package is installed. This | |
# allows packages the opportunity to build platform-specific files and/or binaries. | |
# | |
# If an install file is present, that file will be executed with no arguments and is expected to | |
# install the package. This allows packages to provide arbitrary installation behavior, such as | |
# placing files elsewhere than ~, or just installing packages. | |
# | |
# If no install file is present, then all directories in the package directory will be recursively | |
# created under ~, and all files in the package directories will be symlinked into place. This | |
# behavior is used so that multiple packages can place files in the same directories. | |
# | |
# Profiles | |
# -------------------- | |
# | |
# Profiles are a way to group together multiple packages, as well as to require packages. | |
# | |
# Profiles consist of a directory, containing an optional README.*, a requirements file, and zero or | |
# more package directories. | |
# | |
# The requirements file may consist of # comments, or lines of the format "profiles.d/$PROFILE" or | |
# "packages.d/$PACKAGE". All required packages and profiles will be installed before the packages | |
# included in the profile are installed. | |
# | |
# Example | |
# -------------------- | |
# | |
# $ hostname | |
# test | |
# $ ls hosts.d/test | |
# requirements vim | |
# $ cat host.d/test/requirements | |
# profiles.d/default | |
# packages.d/git | |
# | |
# When install.sh is executed, the host profile named test will be installed. It consists of the | |
# default profile and the git package, as well as a vim package bundled in the host. These packages | |
# will be installed in that order. | |
function arrdem_installf () { | |
# $1 is the logical name of the file to be installed | |
# $2 is the absolute name of the file to be installed | |
# $3 is the absolute name of its destination | |
# | |
# Installs (links) a single file, debugging as appropriate | |
if [ -n "$DEBUG" ]; then | |
echo "[DBG] $1 -> $3" | |
fi | |
ln -s "$2" "$3" | |
} | |
export -f arrdem_installf | |
function arrdem_installd () { | |
# $1 is an un-normalized path which should be created (or cleared!) | |
dir="$(echo $1 | sed 's/\.\///g')" | |
if [ ! -d "$dir" ]; then | |
if [ -n "$FORCE" ]; then | |
rm -rf "$dir" | |
fi | |
mkdir -p "$dir" | |
fi | |
} | |
export -f arrdem_installd | |
function arrdem_stowf () { | |
# $1 is the stow target dir | |
# $2 is the name of the file to stow | |
f="$(echo $2 | sed 's/\.\///g')" # Strip leading ./ | |
TGT="$1/$f" | |
ABS="$(realpath $f)" | |
if [ -h "$TGT" ] || [ -e "$TGT" ]; then | |
if [ "$(realpath $TGT)" != "$ABS" ]; then | |
if [ -n "$FORCE" ]; then | |
if [ -n "$DEBUG" ]; then | |
echo "[DBG] Clobbering existing $ABS" | |
fi | |
rm "$TGT" | |
arrdem_installf "$f" "$ABS" "$TGT" | |
else | |
echo "[WARNING] $TGT already exists! Not replacing!" | |
echo " Would have been replaced with $ABS" | |
fi | |
else | |
if [ -n "$DEBUG" ]; then | |
echo "[DEBUG] $TGT ($f) already installed, skipping" | |
fi | |
fi | |
else | |
arrdem_installf "$f" "$ABS" "$TGT" | |
fi | |
} | |
export -f arrdem_stowf | |
function arrdem_stow () { | |
# $1 is the install target dir | |
# $2 is the source package dir | |
# | |
# Makes all required directories and links all required files to install a given package. | |
# If a target directory doesn't exist, create it as a directory. | |
# For each file in the source, create symlinks into the target directory. | |
# | |
# This has the effect of creating merge mounts between multiple packages, which gnu stow doesn't | |
# support. | |
( cd "$2" | |
# Make all required directories if they don't exist | |
# | |
# If force is set and something is already there blow it the fsck away | |
find . -mindepth 1 \ | |
-type d \ | |
-exec bash -c 'arrdem_installd "$0/$1"' "$1" {} \; | |
# Link in all files. | |
# | |
# If the file already exists AND is a link to the thing we want to install, don't bother. | |
# Else if the file already exists and isn't the thing we want to install and force is set, clobber | |
# Else if the file already exists emit a warning | |
# Else link the file in as appropriate | |
# | |
# Note that this skips install, build and README files | |
find . -type f \ | |
-not -name "INSTALL" \ | |
-not -name "BUILD" \ | |
-not -name "README.*" \ | |
-exec bash -c 'arrdem_stowf "$0" "$1"' "$1" {} \; | |
) | |
} | |
export -f arrdem_stow | |
function install_package() { | |
# $1 is the path of the package to install | |
# | |
# Executes the package build script if present. | |
# | |
# Then executes the install script if present, otherwise using arrdem_stow to install the built | |
# package. | |
echo "[INFO - install_package] installing $1" | |
if [ -x "$1/BUILD" ]; then | |
( cd "$1"; | |
./BUILD) | |
fi | |
if [ -e "$1/INSTALL" ]; then | |
( cd "$1"; | |
./INSTALL) | |
else | |
arrdem_stow ~ "$1" | |
fi | |
} | |
export -f install_package | |
function install_profile() { | |
# $1 is the path of the profile to install | |
# | |
# Reads the requires file from the profile, installing all required profiles and packages, then | |
# installs any packages in the profile itself. | |
if [ -d "$1" ]; then | |
echo "[INFO] installing $1" | |
# Install requires | |
REQUIRES="$1/requires" | |
if [ -e "$REQUIRES" ]; then | |
cat $REQUIRES | while read -r require; do | |
echo "[INFO] $require" | |
case "$require" in | |
profiles.d/*) | |
echo "[INFO - install_profile($1)] recursively installing profile $require" | |
install_profile "$require" | |
;; | |
packages.d/*) | |
echo "[INFO - install_profile($1)] installing package $require" | |
install_package "$require" | |
;; | |
esac | |
done | |
fi | |
# Install the package(s) | |
find "$1" \ | |
-maxdepth 1 \ | |
-mindepth 1 \ | |
-type d \ | |
-exec bash -c 'install_package "$0"' {} \; | |
else | |
echo "[WARN] No such package $1!" | |
fi | |
} | |
function main() { | |
# Normalize hostname | |
HOSTNAME="$(hostname | tr '[:upper:]' '[:lower:]')" | |
HOST_DIR="hosts.d/$HOSTNAME" | |
if [ -d "$HOST_DIR" ]; then | |
# Install the host profile itself | |
# | |
# It is expected that the host requires default explicitly (or transitively) rather than getting | |
# it "for free". | |
echo "[INFO - main] installing profile $HOST_DIR" | |
install_profile "$HOST_DIR" | |
else | |
# Otherwise just install the default profile | |
echo "[INFO - main] installing fallback profile profile profiles.d/default" | |
install_profile "profiles.d/default" | |
fi | |
} | |
main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment