Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Chad Homan 2018-01-29 16:28:41 -06:00
commit 130965e34d
22 changed files with 1067 additions and 257 deletions

10
CHANGES
View File

@ -1,3 +1,13 @@
1.12.0
* Add basic Zsh completion (#71, #79)
* Support directories in `.yadm/encrypt` (#81, #82)
* Support exclusions in `.yadm/encrypt` (#86)
* Improve portability with printf (#87)
* Eliminate usage of `eval` and `ls`
1.11.1
* Create private dirs prior to merge (#74)
1.11.0
* Option for Cygwin to copy files instead of symlink (#62)
* Support `YADM_DISTRO` in Jinja templates (#68)

View File

@ -3,13 +3,15 @@ CONTRIBUTORS
Tim Byrne
Espen Henriksen
Cameron Eagans
Klas Mellbourn
Jan Schulz
Patrick Hof
Satoshi Ohki
Siôn Le Roux
Sébastien Gross
Thomas Luzat
Tomas Cernaj
Uroš Golja
japm48
Franciszek Madej
Klas Mellbourn
Paraplegic Racehorse
Patrick Hof
Satoshi Ohki

View File

@ -1,12 +1,15 @@
.PHONY: all
all: yadm.md contrib
yadm.md: yadm.1
@groff -man -Tascii ./yadm.1 | col -bx | sed 's/^[A-Z]/## &/g' | sed '/yadm(1)/d' > yadm.md
.PHONY: contrib
contrib:
@echo "CONTRIBUTORS\n" > CONTRIBUTORS
@git shortlog -ns master gh-pages dev dev-pages | cut -f2 >> CONTRIBUTORS
.PHONY: pdf
pdf:
@groff -man -Tps ./yadm.1 > yadm.ps
@open yadm.ps
@ -39,8 +42,19 @@ shellcheck:
[ "$$test_result" -ne 0 ] && exit 1; \
done; true
.PHONY: testhost
testhost:
@target=HEAD
@rm -rf /tmp/testhost
@git show $(target):yadm > /tmp/testhost
@chmod a+x /tmp/testhost
@echo Starting testhost target=\"$$target\"
@docker run -w /root --hostname testhost --rm -it -v "/tmp/testhost:/bin/yadm:ro" yadm/testbed:latest bash
.PHONY: man
man:
groff -man -Tascii ./yadm.1 | less
.PHONY: wide
wide:
man ./yadm.1

View File

@ -1,19 +1,36 @@
# Prerequisites
**yadm** completion only works if Git completions are also enabled.
# Installation
## Homebrew
## Bash completions
### Prerequisites
**yadm** completion only works if Git completions are also enabled.
### Homebrew
If using `homebrew` to install **yadm**, completions should automatically be handled if you also install `brew install bash-completion`. This might require you to include the main completion script in your own bashrc file like this:
```
[ -f /usr/local/etc/bash_completion ] && source /usr/local/etc/bash_completion
```
## Manual installation
### Manual installation
Copy the completion script locally, and add this to you bashrc:
```
[ -f /full/path/to/yadm.bash_completion ] && source /full/path/to/yadm.bash_completion
```
## Zsh completions
### Homebrew
If using `homebrew` to install **yadm**, completions should handled automatically.
### Manual installation
Copy the completion script `yadm.zsh_completion` locally, rename it to `_yadm`, and add the containing folder to `$fpath` in `.zshrc`:
```
fpath=(/path/to/folder/containing_yadm $fpath)
autoload -U compinit
compinit
```
### Installation using [zplug](https://github.com/b4b4r07/zplug)
Load `_yadm` as a plugin in your `.zshrc`:
```
fpath=("$ZPLUG_HOME/bin" $fpath)
zplug "TheLocehiliosan/yadm", rename-to:_yadm, use:"completion/yadm.zsh_completion", as:command, defer:2
```

View File

@ -0,0 +1,46 @@
#compdef yadm
_yadm(){
local -a _1st_arguments
_1st_arguments=(
'help:Display yadm command help'
'init:Initialize an empty repository'
'config:Configure a setting'
'list:List tracked files'
'alt:Create links for alternates'
'bootstrap:Execute $HOME/.yadm/bootstrap'
'encrypt:Encrypt files'
'decrypt:Decrypt files'
'perms:Fix perms for private files'
'add:git add'
'push:git push'
'pull:git pull'
'diff:git diff'
'checkout:git checkout'
'co:git co'
'commit:git commit'
'ci:git ci'
'status:git status'
'st:git st'
'reset:git reset'
'log:git log'
)
local context state line expl
local -A opt_args
_arguments '*:: :->subcmds' && return 0
if (( CURRENT == 1 )); then
_describe -t commands "yadm commands" _1st_arguments -V1
return
fi
case "$words[1]" in
*)
_arguments ':filenames:_files'
;;
esac
}
_yadm "$@"

View File

@ -1,66 +0,0 @@
load common
load_fixtures
@test "Default /bin/ls" {
echo "
By default, the value of LS_PROGRAM should be /bin/ls
"
# shellcheck source=/dev/null
YADM_TEST=1 source "$T_YADM"
status=0
output=$( require_ls; echo "$LS_PROGRAM" ) || {
status=$?
true
}
echo "output=$output"
[ "$status" == 0 ]
[ "$output" = "/bin/ls" ]
}
@test "Fallback on 'ls'" {
echo "
When LS_PROGRAM doesn't exist, use 'ls'
"
# shellcheck source=/dev/null
YADM_TEST=1 source "$T_YADM"
status=0
LS_PROGRAM="/ls/missing"
output=$( require_ls; echo "$LS_PROGRAM" ) || {
status=$?
true
}
echo "output=$output"
[ "$status" == 0 ]
[ "$output" = "ls" ]
}
@test "Fail if ls isn't in PATH" {
echo "
When LS_PROGRAM doesn't exist, use 'ls'
"
# shellcheck source=/dev/null
YADM_TEST=1 source "$T_YADM"
status=0
LS_PROGRAM="/ls/missing"
savepath="$PATH"
# shellcheck disable=SC2123
PATH=
output=$( require_ls 2>&1; echo "$LS_PROGRAM" ) || {
status=$?
true
}
PATH="$savepath"
echo "output=$output"
[ "$status" != 0 ]
[[ "$output" =~ functionality\ requires\ .ls.\ to\ be\ installed ]]
}

View File

@ -0,0 +1,318 @@
load common
load_fixtures
setup() {
# SC2153 is intentional
# shellcheck disable=SC2153
make_parents "$T_YADM_ENCRYPT"
make_parents "$T_DIR_WORK"
make_parents "$T_DIR_REPO"
mkdir "$T_DIR_WORK"
git init --shared=0600 --bare "$T_DIR_REPO" >/dev/null 2>&1
GIT_DIR="$T_DIR_REPO" git config core.bare 'false'
GIT_DIR="$T_DIR_REPO" git config core.worktree "$T_DIR_WORK"
GIT_DIR="$T_DIR_REPO" git config yadm.managed 'true'
}
teardown() {
destroy_tmp
}
function run_parse() {
# shellcheck source=/dev/null
YADM_TEST=1 source "$T_YADM"
YADM_ENCRYPT="$T_YADM_ENCRYPT"
export YADM_ENCRYPT
GIT_DIR="$T_DIR_REPO"
export GIT_DIR
# shellcheck disable=SC2034
status=0
{ output=$( parse_encrypt) && parse_encrypt; } || {
status=$?
true
}
if [ "$1" == "twice" ]; then
GIT_DIR="$T_DIR_REPO" parse_encrypt
fi
echo -e "OUTPUT:$output\n"
echo "ENCRYPT_INCLUDE_FILES:"
echo " Size: ${#ENCRYPT_INCLUDE_FILES[@]}"
echo " Items: ${ENCRYPT_INCLUDE_FILES[*]}"
echo "EXPECT_INCLUDE:"
echo " Size: ${#EXPECT_INCLUDE[@]}"
echo " Items: ${EXPECT_INCLUDE[*]}"
}
@test "parse_encrypt (not called)" {
echo "
parse_encrypt() is not called
Array should be 'unparsed'
"
# shellcheck source=/dev/null
YADM_TEST=1 source "$T_YADM"
echo "ENCRYPT_INCLUDE_FILES=$ENCRYPT_INCLUDE_FILES"
[ "$ENCRYPT_INCLUDE_FILES" == "unparsed" ]
}
@test "parse_encrypt (short-circuit)" {
echo "
Parsing should not happen more than once
"
run_parse "twice"
echo "PARSE_ENCRYPT_SHORT: $PARSE_ENCRYPT_SHORT"
[ "$status" == 0 ]
[ "$output" == "" ]
[[ "$PARSE_ENCRYPT_SHORT" =~ not\ reprocessed ]]
}
@test "parse_encrypt (file missing)" {
echo "
.yadm/encrypt is empty
Array should be empty
"
EXPECT_INCLUDE=()
run_parse
[ "$status" == 0 ]
[ "$output" == "" ]
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
}
@test "parse_encrypt (empty file)" {
echo "
.yadm/encrypt is empty
Array should be empty
"
touch "$T_YADM_ENCRYPT"
EXPECT_INCLUDE=()
run_parse
[ "$status" == 0 ]
[ "$output" == "" ]
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
}
@test "parse_encrypt (files)" {
echo "
.yadm/encrypt is references present and missing files
Array should be as expected
"
echo "file1" > "$T_DIR_WORK/file1"
echo "file3" > "$T_DIR_WORK/file3"
echo "file5" > "$T_DIR_WORK/file5"
{ echo "file1"
echo "file2"
echo "file3"
echo "file4"
echo "file5"
} > "$T_YADM_ENCRYPT"
EXPECT_INCLUDE=("file1" "file3" "file5")
run_parse
[ "$status" == 0 ]
[ "$output" == "" ]
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
}
@test "parse_encrypt (files and dirs)" {
echo "
.yadm/encrypt is references present and missing files
.yadm/encrypt is references present and missing dirs
Array should be as expected
"
mkdir -p "$T_DIR_WORK/dir1"
mkdir -p "$T_DIR_WORK/dir2"
echo "file1" > "$T_DIR_WORK/file1"
echo "file2" > "$T_DIR_WORK/file2"
echo "a" > "$T_DIR_WORK/dir1/a"
echo "b" > "$T_DIR_WORK/dir1/b"
{ echo "file1"
echo "file2"
echo "file3"
echo "dir1"
echo "dir2"
echo "dir3"
} > "$T_YADM_ENCRYPT"
EXPECT_INCLUDE=("file1" "file2" "dir1" "dir2")
run_parse
[ "$status" == 0 ]
[ "$output" == "" ]
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
}
@test "parse_encrypt (comments/empty lines)" {
echo "
.yadm/encrypt is references present and missing files
.yadm/encrypt is references present and missing dirs
.yadm/encrypt contains comments / blank lines
Array should be as expected
"
mkdir -p "$T_DIR_WORK/dir1"
mkdir -p "$T_DIR_WORK/dir2"
echo "file1" > "$T_DIR_WORK/file1"
echo "file2" > "$T_DIR_WORK/file2"
echo "file3" > "$T_DIR_WORK/file3"
echo "a" > "$T_DIR_WORK/dir1/a"
echo "b" > "$T_DIR_WORK/dir1/b"
{ echo "file1"
echo "file2"
echo "#file3"
echo " #file3"
echo ""
echo "dir1"
echo "dir2"
echo "dir3"
} > "$T_YADM_ENCRYPT"
EXPECT_INCLUDE=("file1" "file2" "dir1" "dir2")
run_parse
[ "$status" == 0 ]
[ "$output" == "" ]
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
}
@test "parse_encrypt (w/spaces)" {
echo "
.yadm/encrypt is references present and missing files
.yadm/encrypt is references present and missing dirs
.yadm/encrypt references contain spaces
Array should be as expected
"
mkdir -p "$T_DIR_WORK/di r1"
mkdir -p "$T_DIR_WORK/dir2"
echo "file1" > "$T_DIR_WORK/file1"
echo "fi le2" > "$T_DIR_WORK/fi le2"
echo "file3" > "$T_DIR_WORK/file3"
echo "a" > "$T_DIR_WORK/di r1/a"
echo "b" > "$T_DIR_WORK/di r1/b"
{ echo "file1"
echo "fi le2"
echo "#file3"
echo " #file3"
echo ""
echo "di r1"
echo "dir2"
echo "dir3"
} > "$T_YADM_ENCRYPT"
EXPECT_INCLUDE=("file1" "fi le2" "di r1" "dir2")
run_parse
[ "$status" == 0 ]
[ "$output" == "" ]
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
}
@test "parse_encrypt (wildcards)" {
echo "
.yadm/encrypt contains wildcards
Array should be as expected
"
mkdir -p "$T_DIR_WORK/di r1"
mkdir -p "$T_DIR_WORK/dir2"
echo "file1" > "$T_DIR_WORK/file1"
echo "fi le2" > "$T_DIR_WORK/fi le2"
echo "file2" > "$T_DIR_WORK/file2"
echo "file3" > "$T_DIR_WORK/file3"
echo "a" > "$T_DIR_WORK/di r1/a"
echo "b" > "$T_DIR_WORK/di r1/b"
{ echo "fi*"
echo "#file3"
echo " #file3"
echo ""
echo "#dir2"
echo "di r1"
echo "dir2"
echo "dir3"
} > "$T_YADM_ENCRYPT"
EXPECT_INCLUDE=("fi le2" "file1" "file2" "file3" "di r1" "dir2")
run_parse
[ "$status" == 0 ]
[ "$output" == "" ]
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
}
@test "parse_encrypt (excludes)" {
echo "
.yadm/encrypt contains exclusions
Array should be as expected
"
mkdir -p "$T_DIR_WORK/di r1"
mkdir -p "$T_DIR_WORK/dir2"
mkdir -p "$T_DIR_WORK/dir3"
echo "file1" > "$T_DIR_WORK/file1"
echo "file1.ex" > "$T_DIR_WORK/file1.ex"
echo "fi le2" > "$T_DIR_WORK/fi le2"
echo "file3" > "$T_DIR_WORK/file3"
echo "test" > "$T_DIR_WORK/test"
echo "a.txt" > "$T_DIR_WORK/di r1/a.txt"
echo "b.txt" > "$T_DIR_WORK/di r1/b.txt"
echo "c.inc" > "$T_DIR_WORK/di r1/c.inc"
{ echo "fi*"
echo "#file3"
echo " #file3"
echo ""
echo " #test"
echo "#dir2"
echo "di r1/*"
echo "dir2"
echo "dir3"
echo "dir4"
echo "!*.ex"
echo "!di r1/*.txt"
} > "$T_YADM_ENCRYPT"
EXPECT_INCLUDE=("fi le2" "file1" "file3" "di r1/c.inc" "dir2" "dir3")
run_parse
[ "$status" == 0 ]
[ "$output" == "" ]
[ "${#ENCRYPT_INCLUDE_FILES[@]}" -eq "${#EXPECT_INCLUDE[@]}" ]
[ "${ENCRYPT_INCLUDE_FILES[*]}" == "${EXPECT_INCLUDE[*]}" ]
}

View File

@ -440,3 +440,140 @@ EOF
remote_output=$(GIT_DIR="$T_DIR_REPO" git remote show)
[ "$remote_output" = "origin" ]
}
@test "Command 'clone' (local insecure .ssh and .gnupg data, no related data in repo)" {
echo "
Local .ssh/.gnupg data exists and is insecure
but yadm repo contains no .ssh/.gnupg data
local insecure data should remain accessible
(yadm is hands-off)
"
#; setup scenario
rm -rf "$T_DIR_WORK" "$T_DIR_REPO"
mkdir -p "$T_DIR_WORK/.ssh"
mkdir -p "$T_DIR_WORK/.gnupg"
touch "$T_DIR_WORK/.ssh/testfile"
touch "$T_DIR_WORK/.gnupg/testfile"
find "$T_DIR_WORK" -exec chmod a+rw '{}' ';'
#; run clone (with debug on)
run "${T_YADM_Y[@]}" clone -d -w "$T_DIR_WORK" "$REMOTE_URL"
#; validate status and output
[ "$status" -eq 0 ]
[[ "$output" =~ Initialized ]]
[[ "$output" =~ initial\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
[[ "$output" =~ initial\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
# standard perms still apply afterwards unless disabled with auto.perms
test_perms "$T_DIR_WORK/.gnupg" "drwx------"
test_perms "$T_DIR_WORK/.ssh" "drwx------"
}
@test "Command 'clone' (local insecure .gnupg data, related data in repo)" {
echo "
Local .gnupg data exists and is insecure
and yadm repo contains .gnupg data
.gnupg dir should be secured post merge
"
#; setup scenario
IN_REPO=(.bash_profile .vimrc .gnupg/gpg.conf)
setup
rm -rf "$T_DIR_WORK" "$T_DIR_REPO"
mkdir -p "$T_DIR_WORK/.gnupg"
touch "$T_DIR_WORK/.gnupg/testfile"
find "$T_DIR_WORK" -exec chmod a+rw '{}' ';'
#; run clone (with debug on)
run "${T_YADM_Y[@]}" clone -d -w "$T_DIR_WORK" "$REMOTE_URL"
#; validate status and output
[ "$status" -eq 0 ]
[[ "$output" =~ Initialized ]]
[[ "$output" =~ initial\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwxrwxrwx.+\.gnupg ]]
test_perms "$T_DIR_WORK/.gnupg" "drwx------"
}
@test "Command 'clone' (local insecure .ssh data, related data in repo)" {
echo "
Local .ssh data exists and is insecure
and yadm repo contains .ssh data
.ssh dir should be secured post merge
"
#; setup scenario
IN_REPO=(.bash_profile .vimrc .ssh/config)
setup
rm -rf "$T_DIR_WORK" "$T_DIR_REPO"
mkdir -p "$T_DIR_WORK/.ssh"
touch "$T_DIR_WORK/.ssh/testfile"
find "$T_DIR_WORK" -exec chmod a+rw '{}' ';'
#; run clone (with debug on)
run "${T_YADM_Y[@]}" clone -d -w "$T_DIR_WORK" "$REMOTE_URL"
#; validate status and output
[ "$status" -eq 0 ]
[[ "$output" =~ Initialized ]]
[[ "$output" =~ initial\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwxrwxrwx.+\.ssh ]]
test_perms "$T_DIR_WORK/.ssh" "drwx------"
}
@test "Command 'clone' (no existing .gnupg, .gnupg data tracked in repo)" {
echo "
Local .gnupg does not exist
and yadm repo contains .gnupg data
.gnupg dir should be created and secured prior to merge
tracked .gnupg data should be user accessible only
"
#; setup scenario
IN_REPO=(.bash_profile .vimrc .gnupg/gpg.conf)
setup
rm -rf "$T_DIR_WORK"
mkdir -p "$T_DIR_WORK"
rm -rf "$T_DIR_REPO"
#; run clone (with debug on)
run "${T_YADM_Y[@]}" clone -d -w "$T_DIR_WORK" "$REMOTE_URL"
#; validate status and output
[ "$status" -eq 0 ]
[[ "$output" =~ Initialized ]]
[[ ! "$output" =~ initial\ private\ dir\ perms ]]
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwx------.+\.gnupg ]]
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwx------.+\.gnupg ]]
test_perms "$T_DIR_WORK/.gnupg" "drwx------"
}
@test "Command 'clone' (no existing .ssh, .ssh data tracked in repo)" {
echo "
Local .ssh does not exist
and yadm repo contains .ssh data
.ssh dir should be created and secured prior to merge
tracked .ssh data should be user accessible only
"
#; setup scenario
IN_REPO=(.bash_profile .vimrc .ssh/config)
setup
rm -rf "$T_DIR_WORK"
mkdir -p "$T_DIR_WORK"
rm -rf "$T_DIR_REPO"
#; run clone (with debug on)
run "${T_YADM_Y[@]}" clone -d -w "$T_DIR_WORK" "$REMOTE_URL"
#; validate status and output
[ "$status" -eq 0 ]
[[ "$output" =~ Initialized ]]
[[ ! "$output" =~ initial\ private\ dir\ perms ]]
[[ "$output" =~ pre-merge\ private\ dir\ perms\ drwx------.+\.ssh ]]
[[ "$output" =~ post-merge\ private\ dir\ perms\ drwx------.+\.ssh ]]
test_perms "$T_DIR_WORK/.ssh" "drwx------"
}

View File

@ -4,15 +4,20 @@ status=;output=; #; populated by bats run()
IN_REPO=(alt* "dir one")
export TEST_TREE_WITH_ALT=1
EXCLUDED_NAME="excluded-base"
function create_encrypt() {
for efile in "encrypted-base##" "encrypted-system##$T_SYS" "encrypted-host##$T_SYS.$T_HOST" "encrypted-user##$T_SYS.$T_HOST.$T_USER"; do
echo "$efile" >> "$T_YADM_ENCRYPT"
echo "$efile" >> "$T_DIR_WORK/$efile"
mkdir -p "$T_DIR_WORK/dir one/$efile"
echo "'dir one'/$efile/file1" >> "$T_YADM_ENCRYPT"
echo "dir one/$efile/file1" >> "$T_YADM_ENCRYPT"
echo "dir one/$efile/file1" >> "$T_DIR_WORK/dir one/$efile/file1"
done
echo "$EXCLUDED_NAME##" >> "$T_YADM_ENCRYPT"
echo "!$EXCLUDED_NAME##" >> "$T_YADM_ENCRYPT"
echo "$EXCLUDED_NAME##" >> "$T_DIR_WORK/$EXCLUDED_NAME##"
}
setup() {
@ -130,6 +135,12 @@ function test_alt() {
fi
fi
if [ -L "$T_DIR_WORK/$EXCLUDED_NAME" ] ; then
echo "ERROR: Found link: $T_DIR_WORK/$EXCLUDED_NAME"
echo "ERROR: Excluded files should not be linked"
return 1
fi
#; validate link content
if [[ "$alt_type" =~ none ]] || [ "$auto_alt" = "false" ]; then
#; no link should be present

View File

@ -88,6 +88,8 @@ EOF
"$T_GPG_PROGRAM" -q -d "$T_YADM_ARCHIVE" | tar t | sort > "$T_TMP/archive_list"
fi
excluded="$2"
#; inventory what is expected in the archive
(
if cd "$T_DIR_WORK"; then
@ -95,10 +97,23 @@ EOF
# (globbing is desired)
while IFS='' read -r glob || [ -n "$glob" ]; do
if [[ ! $glob =~ ^# && ! $glob =~ ^[[:space:]]*$ ]] ; then
local IFS=$'\n'
for matching_file in $(eval ls "$glob" 2>/dev/null); do
echo "$matching_file"
done
if [[ ! $glob =~ ^!(.+) ]] ; then
local IFS=$'\n'
for matching_file in $glob; do
if [ -e "$matching_file" ]; then
if [ "$matching_file" != "$excluded" ]; then
if [ -d "$matching_file" ]; then
echo "$matching_file/"
for subfile in "$matching_file"/*; do
echo "$subfile"
done
else
echo "$matching_file"
fi
fi
fi
done
fi
fi
done < "$T_YADM_ENCRYPT" | sort > "$T_TMP/expected_list"
fi
@ -290,7 +305,77 @@ EOF
#; add paths with spaces to YADM_ARCHIVE
local original_encrypt
original_encrypt=$(cat "$T_YADM_ENCRYPT")
echo -e "'space test'/file*" >> "$T_YADM_ENCRYPT"
echo -e "space test/file*" >> "$T_YADM_ENCRYPT"
#; run encrypt
run expect <<EOF
set timeout 2;
spawn ${T_YADM_Y[*]} encrypt;
expect "passphrase:" {send "$T_PASSWD\n"}
expect "passphrase:" {send "$T_PASSWD\n"}
expect "$"
foreach {pid spawnid os_error_flag value} [wait] break
exit \$value
EOF
#; validate status and output
[ "$status" -eq 0 ]
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
#; validate the archive
validate_archive symmetric
}
@test "Command 'encrypt' (exclusions in YADM_ENCRYPT)" {
echo "
When 'encrypt' command is provided,
and YADM_ENCRYPT is present
Create YADM_ARCHIVE
Report the archive created
Archive should be valid
Exit with 0
"
#; add paths with spaces to YADM_ARCHIVE
local original_encrypt
original_encrypt=$(cat "$T_YADM_ENCRYPT")
echo -e ".ssh/*" >> "$T_YADM_ENCRYPT"
echo -e "!.ssh/sec*.pub" >> "$T_YADM_ENCRYPT"
#; run encrypt
run expect <<EOF
set timeout 2;
spawn ${T_YADM_Y[*]} encrypt;
expect "passphrase:" {send "$T_PASSWD\n"}
expect "passphrase:" {send "$T_PASSWD\n"}
expect "$"
foreach {pid spawnid os_error_flag value} [wait] break
exit \$value
EOF
#; validate status and output
[ "$status" -eq 0 ]
[[ "$output" =~ Wrote\ new\ file:.+$T_YADM_ARCHIVE ]]
[[ ! "$output" =~ \.ssh/secret.pub ]]
#; validate the archive
validate_archive symmetric ".ssh/secret.pub"
}
@test "Command 'encrypt' (directories in YADM_ENCRYPT)" {
echo "
When 'encrypt' command is provided,
and YADM_ENCRYPT is present
Create YADM_ARCHIVE
Report the archive created
Archive should be valid
Exit with 0
"
#; add directory paths to YADM_ARCHIVE
local original_encrypt
original_encrypt=$(cat "$T_YADM_ENCRYPT")
echo -e "space test" >> "$T_YADM_ENCRYPT"
#; run encrypt
run expect <<EOF

View File

@ -27,13 +27,8 @@ function validate_perms() {
gpg)
restricted=("${restricted[@]}" $T_DIR_WORK/.gnupg $T_DIR_WORK/.gnupg/*)
;;
encrypt)
local glob
while IFS='' read -r glob || [ -n "$glob" ]; do
if [[ ! $glob =~ ^# ]] ; then
restricted=("${restricted[@]}" $T_DIR_WORK/$glob)
fi
done < "$T_YADM_ENCRYPT"
*)
restricted=("${restricted[@]}" $T_DIR_WORK/$p)
;;
esac
done
@ -80,7 +75,7 @@ function validate_perms() {
"
#; this version has a comment in it
echo -e "#.vimrc\n.hammerspoon/*" > "$T_YADM_ENCRYPT"
echo -e "#.vimrc\n.tmux.conf\n.hammerspoon/*\n!.tmux.conf" > "$T_YADM_ENCRYPT"
#; run perms
run "${T_YADM_Y[@]}" perms
@ -89,11 +84,8 @@ function validate_perms() {
[ "$status" -eq 0 ]
[ "$output" = "" ]
#; this version has no comments in it
echo -e ".hammerspoon/*" > "$T_YADM_ENCRYPT"
#; validate permissions
validate_perms ssh gpg encrypt
validate_perms ssh gpg ".hammerspoon/*"
}
@test "Command 'perms' (ssh-perms=false)" {

View File

@ -9,6 +9,11 @@ export TEST_TREE_WITH_ALT=1
setup() {
destroy_tmp
build_repo "${IN_REPO[@]}"
echo "excluded-encrypt##yadm.j2" > "$T_YADM_ENCRYPT"
echo "included-encrypt##yadm.j2" >> "$T_YADM_ENCRYPT"
echo "!excluded-encrypt*" >> "$T_YADM_ENCRYPT"
echo "included-encrypt" > "$T_DIR_WORK/included-encrypt##yadm.j2"
echo "excluded-encrypt" > "$T_DIR_WORK/excluded-encrypt##yadm.j2"
}
@ -27,6 +32,11 @@ function test_alt() {
real_name="alt-jinja"
file_content_match="custom_class-custom_system-custom_host-custom_user-${T_DISTRO}"
;;
encrypt)
real_name="included-encrypt"
file_content_match="included-encrypt"
missing_name="excluded-encrypt"
;;
esac
if [ "$test_overwrite" = "true" ] ; then
@ -63,6 +73,11 @@ function test_alt() {
fi
fi
if [ -n "$missing_name" ] && [ -f "$T_DIR_WORK/$missing_name" ]; then
echo "ERROR: File should not have been created '$missing_name'"
return 1
fi
#; validate link content
if [[ "$alt_type" =~ none ]] || [ "$auto_alt" = "false" ]; then
#; no real file should be present
@ -173,3 +188,16 @@ function test_alt() {
GIT_DIR="$T_DIR_REPO" git config local.class custom_class
test_alt 'override_all' 'false' ''
}
@test "Command 'alt' (select jinja within .yadm/encrypt)" {
echo "
When the command 'alt' is provided
and file matches ##yadm.j2 within .yadm/encrypt
and file excluded within .yadm/encrypt
Report jinja template processing
Verify that the correct content is written
Exit with 0
"
test_alt 'encrypt' 'false' ''
}

View File

@ -73,7 +73,7 @@ function count_introspect() {
Exit with 0
"
count_introspect "configs" 0 12 'yadm\.auto-alt'
count_introspect "configs" 0 13 'yadm\.auto-alt'
}
@test "Command 'introspect' (repo)" {

View File

@ -0,0 +1,102 @@
load common
load_fixtures
status=;output=; #; populated by bats run()
IN_REPO=(.bash_profile .vimrc)
setup() {
destroy_tmp
build_repo "${IN_REPO[@]}"
rm -rf "$T_DIR_WORK"
mkdir -p "$T_DIR_WORK"
}
@test "Private dirs (private dirs missing)" {
echo "
When a git command is run
And private directories are missing
Create private directories prior to command
"
#; confirm directories are missing at start
[ ! -e "$T_DIR_WORK/.gnupg" ]
[ ! -e "$T_DIR_WORK/.ssh" ]
#; run status
export DEBUG=yes
run "${T_YADM_Y[@]}" status
#; validate status and output
[ "$status" -eq 0 ]
[[ "$output" =~ On\ branch\ master ]]
#; confirm private directories are created
[ -d "$T_DIR_WORK/.gnupg" ]
test_perms "$T_DIR_WORK/.gnupg" "drwx------"
[ -d "$T_DIR_WORK/.ssh" ]
test_perms "$T_DIR_WORK/.ssh" "drwx------"
#; confirm directories are created before command is run
[[ "$output" =~ Creating.+/.gnupg/.+Creating.+/.ssh/.+Running\ git\ command\ git\ status ]]
}
@test "Private dirs (private dirs missing / yadm.auto-private-dirs=false)" {
echo "
When a git command is run
And private directories are missing
But auto-private-dirs is false
Do not create private dirs
"
#; confirm directories are missing at start
[ ! -e "$T_DIR_WORK/.gnupg" ]
[ ! -e "$T_DIR_WORK/.ssh" ]
#; set configuration
run "${T_YADM_Y[@]}" config --bool "yadm.auto-private-dirs" "false"
#; run status
run "${T_YADM_Y[@]}" status
#; validate status and output
[ "$status" -eq 0 ]
[[ "$output" =~ On\ branch\ master ]]
#; confirm private directories are not created
[ ! -e "$T_DIR_WORK/.gnupg" ]
[ ! -e "$T_DIR_WORK/.ssh" ]
}
@test "Private dirs (private dirs exist / yadm.auto-perms=false)" {
echo "
When a git command is run
And private directories exist
And yadm is configured not to auto update perms
Do not alter directories
"
#shellcheck disable=SC2174
mkdir -m 0777 -p "$T_DIR_WORK/.gnupg" "$T_DIR_WORK/.ssh"
#; confirm directories are preset and open
[ -d "$T_DIR_WORK/.gnupg" ]
test_perms "$T_DIR_WORK/.gnupg" "drwxrwxrwx"
[ -d "$T_DIR_WORK/.ssh" ]
test_perms "$T_DIR_WORK/.ssh" "drwxrwxrwx"
#; set configuration
run "${T_YADM_Y[@]}" config --bool "yadm.auto-perms" "false"
#; run status
run "${T_YADM_Y[@]}" status
#; validate status and output
[ "$status" -eq 0 ]
[[ "$output" =~ On\ branch\ master ]]
#; confirm directories are still preset and open
[ -d "$T_DIR_WORK/.gnupg" ]
test_perms "$T_DIR_WORK/.gnupg" "drwxrwxrwx"
[ -d "$T_DIR_WORK/.ssh" ]
test_perms "$T_DIR_WORK/.ssh" "drwxrwxrwx"
}

223
yadm
View File

@ -19,7 +19,7 @@ if [ -z "$BASH_VERSION" ]; then
[ "$YADM_TEST" != 1 ] && exec bash "$0" "$@"
fi
VERSION=1.11.0
VERSION=1.12.0
YADM_WORK="$HOME"
YADM_DIR="$HOME/.yadm"
@ -35,13 +35,14 @@ FULL_COMMAND=""
GPG_PROGRAM="gpg"
GIT_PROGRAM="git"
LS_PROGRAM="/bin/ls"
ENVTPL_PROGRAM="envtpl"
LSB_RELEASE_PROGRAM="lsb_release"
PROC_VERSION="/proc/version"
OPERATING_SYSTEM="Unknown"
ENCRYPT_INCLUDE_FILES="unparsed"
#; flag causing path translations with cygpath
USE_CYGPATH=0
@ -128,6 +129,7 @@ function main() {
function alt() {
require_repo
parse_encrypt
local_class="$(config local.class)"
if [ -z "$local_class" ] ; then
@ -160,32 +162,11 @@ function alt() {
match1="^(.+)##(()|$match_system|$match_system\.$match_host|$match_system\.$match_host\.$match_user)$"
match2="^(.+)##($match_class|$match_class\.$match_system|$match_class\.$match_system\.$match_host|$match_class\.$match_system\.$match_host\.$match_user)$"
#; process relative to YADM_WORK
YADM_WORK=$(unix_path "$("$GIT_PROGRAM" config core.worktree)")
cd "$YADM_WORK" || {
debug "Alternates not processed, unable to cd to $YADM_WORK"
return
}
cd_work "Alternates" || return
#; only be noisy if the "alt" command was run directly
[ "$YADM_COMMAND" = "alt" ] && loud="YES"
#; build a list of files from YADM_ENCRYPT
ENC_FILES=()
index=0
if [ -f "$YADM_ENCRYPT" ] ; then
while IFS='' read -r glob || [ -n "$glob" ]; do
if [[ ! $glob =~ ^# && ! $glob =~ ^[[:space:]]*$ ]] ; then
# echo "working on ->$glob<-"
local IFS=$'\n'
for matching_file in $(eval "$LS_PROGRAM" "$glob" 2>/dev/null); do
ENC_FILES[$index]="$matching_file"
((index++))
done
fi
done < "$YADM_ENCRYPT"
fi
#; decide if a copy should be done instead of a symbolic link
local do_copy=0
if [[ $OPERATING_SYSTEM == CYGWIN* ]] ; then
@ -199,7 +180,7 @@ function alt() {
for match in $match1 $match2; do
last_linked=''
local IFS=$'\n'
for tracked_file in $("$GIT_PROGRAM" ls-files | sort) "${ENC_FILES[@]}"; do
for tracked_file in $("$GIT_PROGRAM" ls-files | sort) "${ENCRYPT_INCLUDE_FILES[@]}"; do
tracked_file="$YADM_WORK/$tracked_file"
#; process both the path, and it's parent directory
for alt_path in "$tracked_file" "${tracked_file%/*}"; do
@ -229,7 +210,7 @@ function alt() {
#; for every file which is a *##yadm.j2 create a real file
local IFS=$'\n'
local match="^(.+)##yadm\\.j2$"
for tracked_file in $("$GIT_PROGRAM" ls-files | sort) $(cat "$YADM_ENCRYPT" 2>/dev/null); do
for tracked_file in $("$GIT_PROGRAM" ls-files | sort) "${ENCRYPT_INCLUDE_FILES[@]}"; do
tracked_file="$YADM_WORK/$tracked_file"
if [ -e "$tracked_file" ] ; then
if [[ $tracked_file =~ $match ]] ; then
@ -292,6 +273,8 @@ function clone() {
shift
done
[ -n "$DEBUG" ] && display_private_perms "initial"
#; clone will begin with a bare repo
local empty=
init $empty
@ -310,6 +293,15 @@ function clone() {
rm -rf "$YADM_REPO"
error_out "Unable to fetch origin ${clone_args[0]}"
}
debug "Determining if repo tracks private directories"
for private_dir in .ssh/ .gnupg/; do
found_log=$("$GIT_PROGRAM" log -n 1 origin/master -- "$private_dir" 2>/dev/null)
if [ -n "$found_log" ]; then
debug "Private directory $private_dir is tracked by repo"
assert_private_dirs "$private_dir"
fi
done
[ -n "$DEBUG" ] && display_private_perms "pre-merge"
debug "Doing an initial merge of origin/master"
"$GIT_PROGRAM" merge origin/master || {
debug "Merge failed, doing a reset and stashing conflicts."
@ -351,6 +343,8 @@ EOF
fi
}
[ -n "$DEBUG" ] && display_private_perms "post-merge"
CHANGES_POSSIBLE=1
}
@ -422,14 +416,9 @@ function encrypt() {
require_gpg
require_encrypt
require_ls
parse_encrypt
#; process relative to YADM_WORK
YADM_WORK=$(unix_path "$("$GIT_PROGRAM" config core.worktree)")
cd "$YADM_WORK" || {
debug "Encryption not processed, unable to cd to $YADM_WORK"
return
}
cd_work "Encryption" || return
#; Build gpg options for gpg
GPG_KEY="$(config yadm.gpg-recipient)"
@ -441,26 +430,13 @@ function encrypt() {
GPG_OPTS=("-c")
fi
#; build a list of files from YADM_ENCRYPT
ENC_FILES=()
index=0
while IFS='' read -r glob || [ -n "$glob" ]; do
if [[ ! $glob =~ ^# && ! $glob =~ ^[[:space:]]*$ ]] ; then
local IFS=$'\n'
for matching_file in $(eval "$LS_PROGRAM" "$glob" 2>/dev/null); do
ENC_FILES[$index]="$matching_file"
((index++))
done
fi
done < "$YADM_ENCRYPT"
#; report which files will be encrypted
echo "Encrypting the following files:"
"$LS_PROGRAM" -1 "${ENC_FILES[@]}"
printf '%s\n' "${ENCRYPT_INCLUDE_FILES[@]}"
echo
#; encrypt all files which match the globs
if tar -f - -c "${ENC_FILES[@]}" | $GPG_PROGRAM --yes "${GPG_OPTS[@]}" --output "$YADM_ARCHIVE"; then
if tar -f - -c "${ENCRYPT_INCLUDE_FILES[@]}" | $GPG_PROGRAM --yes "${GPG_OPTS[@]}" --output "$YADM_ARCHIVE"; then
echo "Wrote new file: $YADM_ARCHIVE"
else
error_out "Unable to write $YADM_ARCHIVE"
@ -513,9 +489,18 @@ function git_command() {
set -- "config" "${@:2}"
fi
#; ensure private .ssh and .gnupg directories exist first
#; TODO: consider restricting this to only commands which modify the work-tree
auto_private_dirs=$(config --bool yadm.auto-private-dirs)
if [ "$auto_private_dirs" != "false" ] ; then
assert_private_dirs .gnupg/ .ssh/
fi
CHANGES_POSSIBLE=1
#; pass commands through to git
debug "Running git command $GIT_PROGRAM $*"
"$GIT_PROGRAM" "$@"
return "$?"
}
@ -613,6 +598,7 @@ local.os
local.user
yadm.auto-alt
yadm.auto-perms
yadm.auto-private-dirs
yadm.cygwin-copy
yadm.git-program
yadm.gpg-perms
@ -644,11 +630,7 @@ function list() {
#; process relative to YADM_WORK when --all is specified
if [ -n "$LIST_ALL" ] ; then
YADM_WORK=$(unix_path "$("$GIT_PROGRAM" config core.worktree)")
cd "$YADM_WORK" || {
debug "List not processed, unable to cd to $YADM_WORK"
return
}
cd_work "List" || return
fi
#; list tracked files
@ -658,40 +640,29 @@ function list() {
function perms() {
require_ls
parse_encrypt
#; TODO: prevent repeats in the files changed
#; process relative to YADM_WORK
YADM_WORK=$(unix_path "$("$GIT_PROGRAM" config core.worktree)")
cd "$YADM_WORK" || {
debug "Perms not processed, unable to cd to $YADM_WORK"
return
}
cd_work "Perms" || return
GLOBS=()
#; include the archive created by "encrypt"
[ -f "$YADM_ARCHIVE" ] && GLOBS=("${GLOBS[@]}" "$YADM_ARCHIVE")
[ -f "$YADM_ARCHIVE" ] && GLOBS+=("$YADM_ARCHIVE")
#; include all .ssh files (unless disabled)
if [[ $(config --bool yadm.ssh-perms) != "false" ]] ; then
GLOBS=("${GLOBS[@]}" ".ssh" ".ssh/*")
GLOBS+=(".ssh" ".ssh/*")
fi
#; include all gpg files (unless disabled)
if [[ $(config --bool yadm.gpg-perms) != "false" ]] ; then
GLOBS=("${GLOBS[@]}" ".gnupg" ".gnupg/*")
GLOBS+=(".gnupg" ".gnupg/*")
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 "$LS_PROGRAM" "$glob" 2>/dev/null))
fi
done < "$YADM_ENCRYPT"
fi
#; include any files we encrypt
GLOBS+=("${ENCRYPT_INCLUDE_FILES[@]}")
#; remove group/other permissions from collected globs
#shellcheck disable=SC2068
@ -841,7 +812,7 @@ function set_operating_system() {
CYGWIN*)
git_version=$(git --version 2>/dev/null)
if [[ "$git_version" =~ windows ]] ; then
USE_CYGPATH=1
USE_CYGPATH=1
fi
;;
*)
@ -852,13 +823,13 @@ function set_operating_system() {
function debug() {
[ -n "$DEBUG" ] && echo -e "DEBUG: $*"
[ -n "$DEBUG" ] && echo_e "DEBUG: $*"
}
function error_out() {
echo -e "ERROR: $*"
echo_e "ERROR: $*"
exit_with_hook 1
}
@ -906,6 +877,89 @@ function invoke_hook() {
}
function assert_private_dirs() {
work=$(unix_path "$("$GIT_PROGRAM" config core.worktree)")
for private_dir in "$@"; do
if [ ! -d "$work/$private_dir" ]; then
debug "Creating $work/$private_dir"
#shellcheck disable=SC2174
mkdir -m 0700 -p "$work/$private_dir" >/dev/null 2>&1
fi
done
}
function display_private_perms() {
when="$1"
for private_dir in .ssh .gnupg; do
if [ -d "$YADM_WORK/$private_dir" ]; then
private_perms=$(ls -ld "$YADM_WORK/$private_dir")
debug "$when" private dir perms "$private_perms"
fi
done
}
function cd_work() {
YADM_WORK=$(unix_path "$("$GIT_PROGRAM" config core.worktree)")
cd "$YADM_WORK" || {
debug "$1 not processed, unable to cd to $YADM_WORK"
return 1
}
return 0
}
function parse_encrypt() {
if [ "$ENCRYPT_INCLUDE_FILES" != "unparsed" ]; then
#shellcheck disable=SC2034
PARSE_ENCRYPT_SHORT="parse_encrypt() not reprocessed"
return
fi
ENCRYPT_INCLUDE_FILES=()
ENCRYPT_EXCLUDE_FILES=()
cd_work "Parsing encrypt" || return
exclude_pattern="^!(.+)"
if [ -f "$YADM_ENCRYPT" ] ; then
#; parse both included/excluded
while IFS='' read -r line || [ -n "$line" ]; do
if [[ ! $line =~ ^# && ! $line =~ ^[[:space:]]*$ ]] ; then
local IFS=$'\n'
for pattern in $line; do
if [[ "$pattern" =~ $exclude_pattern ]]; then
for ex_file in ${BASH_REMATCH[1]}; do
if [ -e "$ex_file" ]; then
ENCRYPT_EXCLUDE_FILES+=("$ex_file")
fi
done
else
for in_file in $pattern; do
if [ -e "$in_file" ]; then
ENCRYPT_INCLUDE_FILES+=("$in_file")
fi
done
fi
done
fi
done < "$YADM_ENCRYPT"
#; remove excludes from the includes
#(SC2068 is disabled because in this case, we desire globbing)
FINAL_INCLUDE=()
#shellcheck disable=SC2068
for included in "${ENCRYPT_INCLUDE_FILES[@]}"; do
skip=
#shellcheck disable=SC2068
for ex_file in ${ENCRYPT_EXCLUDE_FILES[@]}; do
[ "$included" == "$ex_file" ] && { skip=1; break; }
done
[ -n "$skip" ] || FINAL_INCLUDE+=("$included")
done
ENCRYPT_INCLUDE_FILES=("${FINAL_INCLUDE[@]}")
fi
}
#; ****** Auto Functions ******
function auto_alt() {
@ -990,13 +1044,6 @@ function require_gpg() {
function require_repo() {
[ -d "$YADM_REPO" ] || error_out "Git repo does not exist. did you forget to run 'init' or 'clone'?"
}
function require_ls() {
if [ ! -f "$LS_PROGRAM" ] ; then
command -v ls >/dev/null 2>&1 || \
error_out "This functionality requires 'ls' to be installed at '$LS_PROGRAM' or listed in your \$PATH"
LS_PROGRAM=ls
fi
}
function require_shell() {
[ -x "$SHELL" ] || error_out "\$SHELL does not refer to an executable."
}
@ -1028,6 +1075,20 @@ function mixed_path() {
fi
}
#; ****** echo replacements ******
function echo() {
IFS=' '
printf '%s\n' "$*"
}
function echo_n() {
IFS=' '
printf '%s' "$*"
}
function echo_e() {
IFS=' '
printf '%b\n' "$*"
}
#; ****** Main processing (when not unit testing) ******
if [ "$YADM_TEST" != 1 ] ; then

45
yadm.1
View File

@ -1,5 +1,5 @@
." vim: set spell so=8:
.TH yadm 1 "10 July 2017" "1.11.0"
.TH yadm 1 "25 October 2017" "1.12.0"
.SH NAME
yadm \- Yet Another Dotfiles Manager
.SH SYNOPSIS
@ -350,6 +350,9 @@ If disabled, you may still run
manually to update permissions.
This feature is enabled by default.
.TP
.B yadm.auto-private-dirs
Disable the automatic creating of private directories described in the section PERMISSIONS.
.TP
.B yadm.ssh-perms
Disable the permission changes to
.IR $HOME/.ssh/* .
@ -583,6 +586,11 @@ For example:
.gnupg/*.gpg
.RE
Standard filename expansions (*, ?, [) are supported. Other shell expansions
like brace and tilde are not supported. Spaces in paths are supported, and
should not be quoted. If a directory is specified, its contents will be
included, but not recursively. Paths beginning with a "!" will be excluded.
The
.B yadm encrypt
command will find all files matching the patterns, and prompt for a password. Once a
@ -608,12 +616,10 @@ It is recommended that you use a private repository when keeping confidential
files, even though they are encrypted.
.SH PERMISSIONS
When files are checked out of a Git repository, their initial permissions are
dependent upon the user's umask. This can result in confidential files with lax permissions.
To prevent this,
dependent upon the user's umask. Because of this,
.B yadm
will automatically update the permissions of confidential files.
The "group" and "others" permissions will be removed from the following files:
will automatically update the permissions of some file paths. The "group" and
"others" permissions will be removed from the following files:
.RI - " $HOME/.yadm/files.gpg
@ -629,11 +635,32 @@ The "group" and "others" permissions will be removed from the following files:
.B yadm
will automatically update permissions by default. This can be disabled using the
.I yadm.auto-perms
configuration.
Even if disabled, permissions can be manually updated by running
configuration. Even if disabled, permissions can be manually updated by running
.BR yadm\ perms .
The SSH directory processing can be disabled using the
The
.I .ssh
directory processing can be disabled using the
.I yadm.ssh-perms
configuration. The
.I .gnupg
directory processing can be disabled using the
.I yadm.gpg-perms
configuration.
When cloning a repo which includes data in a
.IR .ssh " or " .gnupg
directory, if those directories do not exist at the time of cloning,
.B yadm
will create the directories with mask 0700 prior to merging the fetched data
into the work-tree.
When running a Git command and
.IR .ssh " or " .gnupg
directories do not exist,
.B yadm
will create those directories with mask 0700 prior to running the Git command.
This can be disabled using the
.I yadm.auto-private-dirs
configuration.
.SH HOOKS
For every command

157
yadm.md
View File

@ -214,50 +214,54 @@
manually to update permissions. This feature is enabled by
default.
yadm.auto-private-dirs
Disable the automatic creating of private directories described
in the section PERMISSIONS.
yadm.ssh-perms
Disable the permission changes to $HOME/.ssh/*. This feature is
enabled by default.
yadm.gpg-perms
Disable the permission changes to $HOME/.gnupg/*. This feature
Disable the permission changes to $HOME/.gnupg/*. This feature
is enabled by default.
yadm.gpg-recipient
Asymmetrically encrypt files with a gpg public/private key pair.
Provide a "key ID" to specify which public key to encrypt with.
The key must exist in your public keyrings. If left blank or
not provided, symmetric encryption is used instead. If set to
"ASK", gpg will interactively ask for recipients. See the
ENCRYPTION section for more details. This feature is disabled
Provide a "key ID" to specify which public key to encrypt with.
The key must exist in your public keyrings. If left blank or
not provided, symmetric encryption is used instead. If set to
"ASK", gpg will interactively ask for recipients. See the
ENCRYPTION section for more details. This feature is disabled
by default.
yadm.gpg-program
Specify an alternate program to use instead of "gpg". By
Specify an alternate program to use instead of "gpg". By
default, the first "gpg" found in $PATH is used.
yadm.git-program
Specify an alternate program to use instead of "git". By
Specify an alternate program to use instead of "git". By
default, the first "git" found in $PATH is used.
yadm.cygwin-copy
If set to "true", for Cygwin hosts, alternate files will be
copies instead of symbolic links. This might be desirable,
because non-Cygwin software may not properly interpret Cygwin
If set to "true", for Cygwin hosts, alternate files will be
copies instead of symbolic links. This might be desirable,
because non-Cygwin software may not properly interpret Cygwin
symlinks.
These last four "local" configurations are not stored in the
These last four "local" configurations are not stored in the
$HOME/.yadm/config, they are stored in the local repository.
local.class
Specify a CLASS for the purpose of symlinking alternate files.
Specify a CLASS for the purpose of symlinking alternate files.
By default, no CLASS will be matched.
local.os
Override the OS for the purpose of symlinking alternate files.
local.hostname
Override the HOSTNAME for the purpose of symlinking alternate
Override the HOSTNAME for the purpose of symlinking alternate
files.
local.user
@ -268,7 +272,7 @@
to have an automated way of choosing an alternate version of a file for
a different operating system, host, or user. yadm implements a feature
which will automatically create a symbolic link to the appropriate ver-
sion of a file, as long as you follow a specific naming convention.
sion of a file, as long as you follow a specific naming convention.
yadm can detect files with names ending in any of the following:
##
@ -280,10 +284,10 @@
##OS.HOSTNAME
##OS.HOSTNAME.USER
If there are any files managed by yadm's repository, or listed in
If there are any files managed by yadm's repository, or listed in
$HOME/.yadm/encrypt, which match this naming convention, symbolic links
will be created for the most appropriate version. This may best be
demonstrated by example. Assume the following files are managed by
will be created for the most appropriate version. This may best be
demonstrated by example. Assume the following files are managed by
yadm's repository:
- $HOME/path/example.txt##
@ -305,7 +309,7 @@
$HOME/path/example.txt -> $HOME/path/example.txt##Darwin
Since the hostname doesn't match any of the managed files, the more
Since the hostname doesn't match any of the managed files, the more
generic version is chosen.
If running on a Linux server named "host4", the link will be:
@ -323,42 +327,42 @@
If no "##" version exists and no files match the current CLASS/OS/HOST-
NAME/USER, then no link will be created.
Links are also created for directories named this way, as long as they
Links are also created for directories named this way, as long as they
have at least one yadm managed file within them.
CLASS must be manually set using yadm config local.class <class>. OS
is determined by running uname -s, HOSTNAME by running hostname, and
USER by running id -u -n. yadm will automatically create these links
CLASS must be manually set using yadm config local.class <class>. OS
is determined by running uname -s, HOSTNAME by running hostname, and
USER by running id -u -n. yadm will automatically create these links
by default. This can be disabled using the yadm.auto-alt configuration.
Even if disabled, links can be manually created by running yadm alt.
It is possible to use "%" as a "wildcard" in place of CLASS, OS, HOST-
NAME, or USER. For example, The following file could be linked for any
It is possible to use "%" as a "wildcard" in place of CLASS, OS, HOST-
NAME, or USER. For example, The following file could be linked for any
host when the user is "harvey".
$HOME/path/example.txt##%.%.harvey
CLASS is a special value which is stored locally on each host (inside
the local repository). To use alternate symlinks using CLASS, you must
set the value of class using the configuration local.class. This is
CLASS is a special value which is stored locally on each host (inside
the local repository). To use alternate symlinks using CLASS, you must
set the value of class using the configuration local.class. This is
set like any other yadm configuration with the yadm config command. The
following sets the CLASS to be "Work".
yadm config local.class Work
Similarly, the values of OS, HOSTNAME, and USER can be manually over-
ridden using the configuration options local.os, local.hostname, and
Similarly, the values of OS, HOSTNAME, and USER can be manually over-
ridden using the configuration options local.os, local.hostname, and
local.user.
## JINJA
If the envtpl command is available, Jinja templates will also be pro-
If the envtpl command is available, Jinja templates will also be pro-
cessed to create or overwrite real files. yadm will treat files ending
in
##yadm.j2
as Jinja templates. During processing, the following variables are set
as Jinja templates. During processing, the following variables are set
according to the rules explained in the ALTERNATES section:
YADM_CLASS
@ -366,7 +370,7 @@
YADM_HOSTNAME
YADM_USER
In addition YADM_DISTRO is exposed as the value of lsb_release -si if
In addition YADM_DISTRO is exposed as the value of lsb_release -si if
lsb_release is locally available.
For example, a file named whatever##yadm.j2 with the following content
@ -377,7 +381,7 @@
config=dev-whatever
{% endif -%}
would output a file named whatever with the following content if the
would output a file named whatever with the following content if the
user is "harvey":
config=work-Linux
@ -390,45 +394,48 @@
## ENCRYPTION
It can be useful to manage confidential files, like SSH or GPG keys,
across multiple systems. However, doing so would put plain text data
into a Git repository, which often resides on a public system. yadm
implements a feature which can make it easy to encrypt and decrypt a
set of files so the encrypted version can be maintained in the Git
repository. This feature will only work if the gpg(1) command is
It can be useful to manage confidential files, like SSH or GPG keys,
across multiple systems. However, doing so would put plain text data
into a Git repository, which often resides on a public system. yadm
implements a feature which can make it easy to encrypt and decrypt a
set of files so the encrypted version can be maintained in the Git
repository. This feature will only work if the gpg(1) command is
available.
To use this feature, a list of patterns must be created and saved as
$HOME/.yadm/encrypt. This list of patterns should be relative to the
To use this feature, a list of patterns must be created and saved as
$HOME/.yadm/encrypt. This list of patterns should be relative to the
configured work-tree (usually $HOME). For example:
.ssh/*.key
.gnupg/*.gpg
Standard filename expansions (*, ?, [) are supported. Other shell
expansions like brace and tilde are not supported. Spaces in paths are
supported, and should not be quoted. If a directory is specified, its
contents will be included, but not recursively. Paths beginning with a
"!" will be excluded.
The yadm encrypt command will find all files matching the patterns, and
prompt for a password. Once a password has confirmed, the matching
files will be encrypted and saved as $HOME/.yadm/files.gpg. The pat-
terns and files.gpg should be added to the yadm repository so they are
prompt for a password. Once a password has confirmed, the matching
files will be encrypted and saved as $HOME/.yadm/files.gpg. The pat-
terns and files.gpg should be added to the yadm repository so they are
available across multiple systems.
To decrypt these files later, or on another system run yadm decrypt and
provide the correct password. After files are decrypted, permissions
provide the correct password. After files are decrypted, permissions
are automatically updated as described in the PERMISSIONS section.
Symmetric encryption is used by default, but asymmetric encryption may
Symmetric encryption is used by default, but asymmetric encryption may
be enabled using the yadm.gpg-recipient configuration.
NOTE: It is recommended that you use a private repository when keeping
NOTE: It is recommended that you use a private repository when keeping
confidential files, even though they are encrypted.
## PERMISSIONS
When files are checked out of a Git repository, their initial permis-
sions are dependent upon the user's umask. This can result in confiden-
tial files with lax permissions.
To prevent this, yadm will automatically update the permissions of con-
fidential files. The "group" and "others" permissions will be removed
from the following files:
When files are checked out of a Git repository, their initial permis-
sions are dependent upon the user's umask. Because of this, yadm will
automatically update the permissions of some file paths. The "group"
and "others" permissions will be removed from the following files:
- $HOME/.yadm/files.gpg
@ -439,26 +446,38 @@
- The GPG directory and files, .gnupg/*
yadm will automatically update permissions by default. This can be dis-
abled using the yadm.auto-perms configuration. Even if disabled, per-
missions can be manually updated by running yadm perms. The SSH direc-
tory processing can be disabled using the yadm.ssh-perms configuration.
abled using the yadm.auto-perms configuration. Even if disabled, per-
missions can be manually updated by running yadm perms. The .ssh
directory processing can be disabled using the yadm.ssh-perms configu-
ration. The .gnupg directory processing can be disabled using the
yadm.gpg-perms configuration.
When cloning a repo which includes data in a .ssh or .gnupg directory,
if those directories do not exist at the time of cloning, yadm will
create the directories with mask 0700 prior to merging the fetched data
into the work-tree.
When running a Git command and .ssh or .gnupg directories do not exist,
yadm will create those directories with mask 0700 prior to running the
Git command. This can be disabled using the yadm.auto-private-dirs
configuration.
## HOOKS
For every command yadm supports, a program can be provided to run
before or after that command. These are referred to as "hooks". yadm
For every command yadm supports, a program can be provided to run
before or after that command. These are referred to as "hooks". yadm
looks for hooks in the directory $HOME/.yadm/hooks. Each hook is named
using a prefix of pre_ or post_, followed by the command which should
trigger the hook. For example, to create a hook which is run after
every yadm pull command, create a hook named post_pull. Hooks must
using a prefix of pre_ or post_, followed by the command which should
trigger the hook. For example, to create a hook which is run after
every yadm pull command, create a hook named post_pull. Hooks must
have the executable file permission set.
If a pre_ hook is defined, and the hook terminates with a non-zero exit
status, yadm will refuse to run the yadm command. For example, if a
pre_commit hook is defined, but that command ends with a non-zero exit
status, the yadm commit will never be run. This allows one to "short-
status, yadm will refuse to run the yadm command. For example, if a
pre_commit hook is defined, but that command ends with a non-zero exit
status, the yadm commit will never be run. This allows one to "short-
circuit" any operation using a pre_ hook.
Hooks have the following environment variables available to them at
Hooks have the following environment variables available to them at
runtime:
YADM_HOOK_COMMAND
@ -477,8 +496,8 @@
The path to the work-tree
## FILES
The following are the default paths yadm uses for its own data. These
paths can be altered using universal options. See the OPTIONS section
The following are the default paths yadm uses for its own data. These
paths can be altered using universal options. See the OPTIONS section
for details.
$HOME/.yadm

View File

@ -1,6 +1,6 @@
Summary: Yet Another Dotfiles Manager
Name: yadm
Version: 1.11.0
Version: 1.12.0
Release: 1%{?dist}
URL: https://github.com/TheLocehiliosan/yadm
License: GPLv3
@ -34,10 +34,17 @@ install -m 644 yadm.1 ${RPM_BUILD_ROOT}%{_mandir}/man1
%attr(755,root,root) %{_bindir}/yadm
%attr(644,root,root) %{_mandir}/man1/*
%license LICENSE
%doc CHANGES CONTRIBUTORS README.md completion/yadm.bash_completion
%doc CHANGES CONTRIBUTORS README.md completion/*
%changelog
* Mon July 10 2017 Tim Byrne <sultan@locehilios.com> - 1.11.0-1
* Wed Oct 25 2017 Tim Byrne <sultan@locehilios.com> - 1.12.0-1
- Bump version to 1.12.0
- Include zsh completion
* Wed Aug 23 2017 Tim Byrne <sultan@locehilios.com> - 1.11.1-1
- Bump version to 1.11.1
* Mon Jul 10 2017 Tim Byrne <sultan@locehilios.com> - 1.11.0-1
- Bump version to 1.11.0
* Wed May 10 2017 Tim Byrne <sultan@locehilios.com> - 1.10.0-1