commit
c941690295
@ -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 <http://www.gnu.org/licenses/>. |
@ -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 <http://www.gnu.org/licenses/>. |
||||
|
||||
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 "<file>##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 <<EOF |
||||
**NOTE** |
||||
Merging origin/master failed. |
||||
YADM did 'reset origin/master' instead. |
||||
|
||||
This likely happened because you had files in your |
||||
work-tree, which conflict files tracked by origin/master |
||||
|
||||
Please review and resolve any differences appropriately |
||||
If you know what you're doing, and want to overwrite the |
||||
tracked files, consider 'yadm reset --hard origin/master' |
||||
EOF |
||||
} |
||||
|
||||
CHANGES_POSSIBLE=1 |
||||
|
||||
} |
||||
|
||||
function config() { |
||||
|
||||
#; ensure we have a file, even if empty |
||||
[ -f "$YADM_CONFIG" ] || touch "$YADM_CONFIG" |
||||
|
||||
if [ -z "$@" ] ; then |
||||
#; with no parameters, provide some helpful documentation |
||||
echo TODO: Print help about available YADM configurations |
||||
else |
||||
#; operate on the YADM configuration file |
||||
git config --file="$YADM_CONFIG" $@ |
||||
fi |
||||
|
||||
} |
||||
|
||||
function decrypt() { |
||||
|
||||
require_gpg |
||||
require_archive |
||||
|
||||
YADM_WORK=$(git config core.worktree) |
||||
|
||||
#; decrypt the archive |
||||
(gpg -d "$YADM_ARCHIVE" || echo 1) | tar xv -C "$YADM_WORK" |
||||
if [ $? = 0 ] ; then |
||||
echo "All files decrypted." |
||||
else |
||||
error_out "Unable to extract encrypted files." |
||||
fi |
||||
|
||||
CHANGES_POSSIBLE=1 |
||||
|
||||
} |
||||
|
||||
function encrypt() { |
||||
|
||||
require_gpg |
||||
require_encrypt |
||||
|
||||
#; process relative to YADM_WORK |
||||
YADM_WORK=$(git config core.worktree) |
||||
cd $YADM_WORK |
||||
|
||||
#; build a list of globs from YADM_ENCRYPT |
||||
GLOBS=() |
||||
while IFS='' read -r glob || [ -n "$glob" ]; do |
||||
if [[ ! $glob =~ ^# ]] ; then |
||||
GLOBS=("${GLOBS[@]}" $(eval /bin/ls "$glob" 2>/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 "$@" |
@ -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] <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 |
||||
.SH DESCRIPTION |
||||
.SH OPTIONS |
||||
.SH SEE ALSO |
||||
.SH REPORTING BUGS |
||||
.SH AUTHOR |
||||
Tim Byrne (sultan@locehilios.com) |
||||
|
Loading…
Reference in new issue