From c9416902953a629d68b9366cda8a69eb3752eba5 Mon Sep 17 00:00:00 2001 From: Tim Byrne Date: Tue, 14 Jul 2015 07:48:47 -0500 Subject: [PATCH] Create first public version --- LICENSE | 14 ++ README.md | 1 + yadm | 436 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ yadm.1 | 32 ++++ 4 files changed, 483 insertions(+) create mode 100644 LICENSE create mode 100644 README.md create mode 100755 yadm create mode 100644 yadm.1 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c7efe34 --- /dev/null +++ b/LICENSE @@ -0,0 +1,14 @@ +yadm - Yet Another Dotfiles Manager +Copyright (C) 2015 Tim Byrne + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, version 3 of the License. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see . diff --git a/README.md b/README.md new file mode 100644 index 0000000..ccc0b23 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# yadm - Yet Another Dotfiles Manager diff --git a/yadm b/yadm new file mode 100755 index 0000000..3b8e310 --- /dev/null +++ b/yadm @@ -0,0 +1,436 @@ +#!/bin/bash + +# yadm - Yet Another Dotfiles Manager +# Copyright (C) 2015 Tim Byrne + +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, version 3 of the License. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +VERSION=1.00 + +YADM_WORK="$HOME" +YADM_DIR="$HOME/.yadm" + +YADM_REPO="$YADM_DIR/repo.git" +YADM_CONFIG="$YADM_DIR/config" +YADM_ENCRYPT="$YADM_DIR/encrypt" +YADM_ARCHIVE="$YADM_DIR/files.gpg" + +#; flag when something may have changes (which prompts auto actions to be performed) +CHANGES_POSSIBLE=0 + +#; use the YADM repo for all git operations +export GIT_DIR="$YADM_REPO" + +function main() { + + require_git + + #; create the YADM_DIR if it doesn't exist yet + [ -d "$YADM_DIR" ] || mkdir -p $YADM_DIR + + #; parse command line arguments + internal_commands="^(alt|clean|clone|config|decrypt|encrypt|help|init|list|perms|version)$" + if [ -z "$*" ] ; then + #; no argumnts will result in help() + help + elif [ "$1" == "gitconfig" ] ; then + #; 'config' is used for yadm, need to use 'gitcofnig' to pass through to git + shift + git_command config "$@" + elif [[ "$1" =~ $internal_commands ]] ; then + #; for internal commands, process all of the arguments + YADM_COMMAND="$1" + YADM_ARGS="" + shift + + while [[ $# > 0 ]] ; do + key="$1" + case $key in + -a|--all) #; used by list() + LIST_ALL="YES" + ;; + -d|--debug) #; used by all commands + DEBUG="YES" + ;; + -f|--force) #; used by init() and clone() + FORCE="YES" + ;; + -w|--work-tree) #; used by init() and clone() + if [[ ! "$2" =~ ^/ ]] ; then + error_out "You must specify a fully qualified work tree" + fi + YADM_WORK="$2" + shift + ;; + *) #; any unhandled arguments + if [ -z "$YADM_ARGS" ] ; then + YADM_ARGS="$1" + else + YADM_ARGS+=" $1" + fi + ;; + esac + shift + done + [ ! -d $YADM_WORK ] && error_out "Work tree does not exist: [$YADM_WORK]" + $YADM_COMMAND "$YADM_ARGS" + else + #; any other commands are simply passed through to git + git_command "$@" + fi + + #; process automatic events + auto_alt + auto_perms + +} + +#; ****** YADM Commands ****** + +function alt() { + + require_repo + + #; regex for matching "##SYSTEM.HOSTNAME" + match_system=$(uname -s) + match_host=$(hostname) + match="^(.+)##($match_system|$match_system.$match_host)$" + + #; process relative to YADM_WORK + YADM_WORK=$(git config core.worktree) + cd $YADM_WORK + + #; only be noisy if the "alt" command was run directly + [ "$YADM_COMMAND" == "alt" ] && LOUD="YES" + + #; loop over all "tracked" files + #; for every file which matches the above regex, create a symlink + for tracked_file in $(git ls-files | sort); do + tracked_file="$YADM_WORK/$tracked_file" + if [[ $tracked_file =~ $match ]] ; then + new_link="${BASH_REMATCH[1]}" + debug "Linking $tracked_file to $new_link" + [ -n "$LOUD" ] && echo "Linking $tracked_file to $new_link" + ln -fs "$tracked_file" "$new_link" + fi + done + +} + +function clean() { + + error_out "\"git clean\" has been disabled for safety. You could end up removing all unmanaged files." + +} + +function clone() { + + #; clone will begin with a bare repo + init + + #; add the specified remote, and configure the repo to track origin/master + debug "Adding remote to new repo" + git remote add origin "$1" + debug "Configuring new repo to track origin/master" + git config branch.master.remote origin + git config branch.master.merge refs/heads/master + + #; fetch / merge (and possibly fallback to reset) + debug "Doing an initial fetch of the origin" + git fetch origin + git merge origin/master || { + git reset origin/master + echo </dev/null)) + fi + done < "$YADM_ENCRYPT" + + #; encrypt all files which match the globs + tar -cv ${GLOBS[@]} | gpg --yes -c --output "$YADM_ARCHIVE" + if [ $? = 0 ]; then + echo "Wrote new file: $YADM_ARCHIVE" + else + error_out "Unable to write $YADM_ARCHIVE" + fi + + CHANGES_POSSIBLE=1 + +} + +function git_command() { + + require_repo + + #; pass commands through to git + git "$@" + + CHANGES_POSSIBLE=1 + +} + +function help() { + + cat << EOF +Usage: yadm [COMMAND] [OPTIONS ...] + +Manage dotfiles maintained in a Git repository. Manage alternate files +for specific systems or hosts. Encrypt/decrypt private files. + +Git Commands: +Any Git command or alias can be used as a [COMMAND]. It will operate +on YADM's repository and files in the work tree (usually \$HOME). + +Commands: + init [-f] - Initialize an empty repository + clone [-f] GIT_URL - Clone an existing repository + config [name] [value] - Configure a setting + list [-a] - List tracked files + alt - Create links for alternates + encrypt - Encrypt files + decrypt - Decrypt files + perms - Fix perms for private files + +Paths: + \$HOME/.yadm/config - YADM's configuration file + \$HOME/.yadm/repo.git - YADM's Git repository + \$HOME/.yadm/encrypt - List of globs used for encrypt/decrypt + +Use "man yadm" for complete documentation. +EOF + + exit 1 + +} + +function init() { + + #; safety check, don't attempt to init when the repo is already present + [ -d "$YADM_REPO" ] && [ -z "$FORCE" ] && \ + error_out "Git repo already exist. [$YADM_REPO]\nUse '-f' if you want to force it to be overwritten." + + #; remove existing if forcing the init to happen anyway + [ -d "$YADM_REPO" ] && { + debug "Removing existing repo prior to init" + rm -rf "$YADM_REPO" + } + + #; init a new bare repo + debug "Init new repo" + git init --shared=0600 --bare "$YADM_REPO" + configure_repo + + CHANGES_POSSIBLE=1 + +} + +function list() { + + require_repo + + #; process relative to YADM_WORK when --all is specified + if [ -n "$LIST_ALL" ] ; then + YADM_WORK=$(git config core.worktree) + cd $YADM_WORK + fi + + #; list tracked files + git ls-files + +} + +function perms() { + + #; TODO: prevent repeats in the files changed + + #; process relative to YADM_WORK + YADM_WORK=$(git config core.worktree) + cd $YADM_WORK + + GLOBS=() + + #; include the archive created by "encrypt" + [ -f "$YADM_ARCHIVE" ] && GLOBS=("${GLOBS[@]}" "$YADM_ARCHIVE") + + #; include all .ssh files (unless disabled) + if [[ $(config yadm.ssh-perms) != "false" ]] ; then + GLOBS=("${GLOBS[@]}" $(eval /bin/ls ".ssh/*" 2>/dev/null)) + fi + + #; include globs found in YADM_ENCRYPT (if present) + if [ -f "$YADM_ENCRYPT" ] ; then + while IFS='' read -r glob || [ -n "$glob" ]; do + if [[ ! $glob =~ ^# ]] ; then + GLOBS=("${GLOBS[@]}" $(eval /bin/ls "$glob" 2>/dev/null)) + fi + done < "$YADM_ENCRYPT" + fi + + #; remove group/other permissions from collected globs + perms_modified=$(chmod -v go-rwx ${GLOBS[@]}) + + #; report any changed permissions + if [ -n "$perms_modified" ] ; then + echo "Updated permissions:" + ls -l $perms_modified | sort | uniq + fi + +} + +function version() { + + echo "yadm $VERSION" + exit 0 + +} + +#; ****** Utility Functions ****** + +function configure_repo() { + + debug "Configuring new repo" + + #; change bare to false (there is a working directory) + git config core.bare 'false' + + #; set the worktree for the YADM repo + git config core.worktree "$YADM_WORK" + + #; possibly used later to ensure we're working on the YADM repo + git config yadm.managed 'true' + +} + +function debug() { + + [ -n "$DEBUG" ] && echo -e "DEBUG: $@" + +} + +function error_out() { + + echo -e "ERROR: $@" + exit 1 + +} + +#; ****** Auto Functions ****** + +function auto_alt() { + + #; process alternates if there are possible changes + if [ "$CHANGES_POSSIBLE" == "1" ] ; then + auto_alt=$(config yadm.auto-alt) + if [ "$auto_alt" != "false" ] ; then + alt + fi + fi + +} + +function auto_perms() { + + #; process permissions if there are possible changes + if [ "$CHANGES_POSSIBLE" == "1" ] ; then + auto_perms=$(config yadm.auto-perms) + if [ "$auto_perms" != "false" ] ; then + perms + fi + fi + +} + +#; ****** Prerequisites Functions ****** + +function require_archive() { + [ -f "$YADM_ARCHIVE" ] || error_out "$YADM_ARCHIVE does not exist. did you forget to create it?" +} +function require_encrypt() { + [ -f "$YADM_ENCRYPT" ] || error_out "$YADM_ENCRYPT does not exist. did you forget to create it?" +} +function require_git() { + command -v git >/dev/null 2>&1 || \ + error_out "This functionality requires Git to be installed, but the command git cannot be located." +} +function require_gpg() { + command -v gpg >/dev/null 2>&1 || \ + error_out "This functionality requires GPG to be installed, but the command gpg cannot be located." +} +function require_repo() { + [ -d "$YADM_REPO" ] || error_out "Git repo does not exist. did you forget to run 'init' or 'clone'?" +} + +main "$@" diff --git a/yadm.1 b/yadm.1 new file mode 100644 index 0000000..f8d2707 --- /dev/null +++ b/yadm.1 @@ -0,0 +1,32 @@ +.TH yadm 1 "14 July 2015" "1.00" +.SH NAME +yadm \- Yet Another Dotfiles Manager +.SH SYNOPSIS +Manage dotfiles maintained in a Git repository. Manage alternate files for +specific systems or hosts. Encrypt/decrypt private files. + +Git Commands: +Any Git command or alias can be used as a [COMMAND]. It will operate on YADM's +repository and files in the work tree (usually $HOME). + +Commands: + init [-f] - Initialize an empty repository + clone [-f] - Clone an existing repository + config [name] [value] - Configure a setting + list [-a] - List tracked files + alt - Create links for alternates + encrypt - Encrypt files + decrypt - Decrypt files + perms - Fix perms for private files + +Paths: + $HOME/.yadm/config - YADM's configuration file + $HOME/.yadm/repo.git - YADM's Git repository + $HOME/.yadm/encrypt - List of globs used for encrypt/decrypt +.SH DESCRIPTION +.SH OPTIONS +.SH SEE ALSO +.SH REPORTING BUGS +.SH AUTHOR +Tim Byrne (sultan@locehilios.com) +