use /tmp if $HOME is not set
[travelrc.git] / README.md
1 # travelrc – take your rc-files with you when using ssh or su
2
3 ## What is the problem?
4
5 Over the years, I massively configure commandline tools that I regularly use, like bash, vim, screen an so on.
6 I created aliases, a custom prompt, special keybindings and commands.
7 It fits my workflow very well.
8 But every time I switch to another system (ssh to a local VM, or cloud-VM, or a docker container; changing to root) none of these settings work anymore and I'm annoyed.
9
10 ## How can it be solved?
11
12 I put my rc-files in a git repo and cloned it on systems that I use regularly.
13 It works, I just have to pull my latest changes a lot.
14 But it does not work on temporal systems or on systems where I do not want to clone (or can't clone) all my rc-files.
15 The requirements that I came up with for traveling with my rc-files are like this:
16
17 * only save travelled rc-files temporarily
18 * it's okay to travel with just the most important rc-files
19 * do not interfere with already existing rc-files (especially for shared accounts like root)
20 * use it with ssh, su (changing to root or another user on the same machine), docker, and probably more
21 * do not add too much steps in the process (no rollout via ansible, no scp then ssh), keep it ephemeral
22
23 ## How did I solve it?
24
25 I searched the internet for solutions to my problem, found something (https://github.com/IngoMeyer441/sshrc) and extended and (in my opinion) improved it.
26
27 The basic idea is to minify (remove comments), compress and tar the rc-files,
28 convert this archive to a (long) string (base64 encode),
29 and add this string and commands to decompress and extract it as a command to ssh (or su/docker).
30
31 My solution requires some steps which I'll outline here.
32 It can be used as is, but it can also act as an inspiration for other solutions.
33
34 * create a directory (the name of the directory is used once in the `minify-trc` function below) and symlink (or copy) the rc-files, that should travel with you, into it
35
36 ```bash
37 mkdir $HOME/.travelrc
38 ln -s -t $HOME/.travelrc $HOME/{.bashrc,.inputrc,.vimrc,.screenrc,.tmux.conf}
39 ```
40
41 * add a directory with executables that should be available in the travelled session
42
43 ```bash
44 mkdir $HOME/.travelrc/trcbin
45 echo '#!/bin/bash
46 bash --rcfile "$TBASHRC"' > $HOME/.travelrc/trcbin/trcbash
47 chmod +x $HOME/.travelrc/trcbin/trcbash
48 ```
49
50 * add helper functions to your `.bashrc`
51
52 ```bash
53 minify-trc() {
54     # this function copies the trc files to a tempdir and minifies them, it returns the tempdirname on stdout
55     local trcdir="${TRAVELRCDIR:-$HOME/.travelrc}" # use the travelled rc dir if already travelled
56     [ -f "$trcdir/.bashrc" ] || { echo "file .bashrc inside trcdir \"$trcdir\" does not exist" >&2; return 1; }
57     # remove all comments from the files to decrease size, has to be done carefully (comment characters can be inside quotes or other commands)
58     echo -n "minifying trcdir... " >&2
59     local trc_min_dir="$(mktemp --directory "/tmp/.trc.min.XXXXXX")"
60     cp --dereference --recursive --target-directory="$trc_min_dir" "$trcdir/"*
61     [ -f "$trc_min_dir/.vimrc" ] && sed --in-place -e 's/^ *".*$//' -e 's/ \+" .*$//' "$trc_min_dir/.vimrc" # special minifyer for vimrc
62     find "$trc_min_dir/" -type f -exec sed --in-place -e 's/^ *#[^!].*$//' -e 's/ \+# .*$//' '{}' '+' # minify all files as if they were shell scripts
63     echo "$trc_min_dir"
64 }
65
66 trccmd() {
67     # echo all the commands to travel with rc files, arguments act as additional arguments to tar
68     local trc_min_dir="$(minify-trc)"
69     echo -n "packing trcdir with '$*'... " >&2
70     [ -n "$1" ] || set -- "--gzip" # default compression is gzip (when no argument is set), can be disabled with "-a" which leads to no compression
71     local trcvar="$(tar --create --file=- "$@" --directory="$trc_min_dir" --dereference ./ | base64 --wrap=0)" # this writes the compressed contents of $trc_min_dir base64-encoded to $trcvar
72     rm -rf "$trc_min_dir" # delete the temporal directory generated by minify-trc
73     echo "to ${#trcvar} bytes... " >&2
74     [ ${#trcvar} -lt 65536 ] || { echo "ERROR: content of trcdir '$trcdir' is too big, even after minifying and compressing with '$*'" >&2; return 1; }
75     # export $TRAVELRCDIR and create this directory, it could also be created in /tmp
76     echo '
77 export TRAVELRCDIR=${HOME:-/tmp}/.travelrc.travelled
78 readonly TRAVELRCDIR
79 mkdir --parents $TRAVELRCDIR'
80     # SSH_TTY should still be set to figure out whether this is a ssh session
81     [ -z "$SSH_TTY" ] || echo "export SSH_TTY=$SSH_TTY"
82     # decompress the files saved to $trcvar; start a bash with the travelled rc-file; only remove $TRAVELRCDIR if there is no screen or tmux session (which can still use the files); last command is true so that the returncode is always 0
83     # TODO: is it save to always remove $TRAVELRCDIR because it will be recreated on the next connection?
84     echo '
85 echo '"$trcvar"' | base64 --decode | tar --file=- --extract '"$@"' --touch --directory=$TRAVELRCDIR
86 chmod --quiet --recursive go= $TRAVELRCDIR
87 bash --rcfile $TRAVELRCDIR/.bashrc
88 screen -ls 1>/dev/null 2>&1 || tmux has-session 1>/dev/null 2>&1 || rm -rf $TRAVELRCDIR
89 true
90 '
91 }
92 ```
93
94 * add functions to your `.bashrc` that enable travelling with the rc files via ssh, su, and docker
95     * I prepended a „t“ to each of these commands, so when I type `ssh host` and realize that my rc-files are missing, I can disconnect, prepend a „t“ tho the previous command (to get `tssh host`) and everything is better :-)
96     * this code has to be added after the helper functions mentioned above
97
98 ```bash
99 tssh() {
100     # use -t to force the allocation of a terminal
101     ssh -t "$@" "$(trccmd --xz)"
102 }
103 _tssh_completion() {
104     # when completion is requested, load completion for ssh and use it for tssh, otherwise disable completion for tssh
105     # see https://stackoverflow.com/questions/61539494/how-does-bash-do-ssh-autocompletion
106     if __load_completion "ssh"; then
107         complete -F _ssh tssh
108         return 124
109     else
110         complete -r tssh
111     fi
112 }
113 complete -F _tssh_completion tssh # this just loads the correct completion function
114
115 tdocker() {
116     local dcmd="$1"
117     shift
118     # some docker images don't come with --xz support, so --gzip is used
119     docker "$dcmd" --interactive --tty "$@" bash -c "$(trccmd --gzip)"
120 }
121 # TODO: add completion
122
123 tsu() { # travel substitude user
124     local next_user="${1:-root}"
125     local tsu_cmd="$(mktemp "/tmp/.tsu-cmd.XXXXXX")"
126     # create a script as tsu_cmd, which should always return 0 to be able to tell whether tsu was successful
127     trccmd --gzip > "$tsu_cmd"
128     chmod --quiet ugo=rx "$tsu_cmd"
129     # try several ways to change the user and execute the tsu_cmd
130     local tsu_done="false"
131     if type -P sudo &>/dev/null; then
132         # the groups test might not be very accurate, but it is quiet; sudo is usually allowed for user vagrant
133         # the sudo test is accurate, but creates log messages, which might not be desired, especially when sudo is not allowed
134         if groups | \grep --quiet -e "sudo" -e "admin" -e "wheel" || [ "$USER" = "vagrant" ] #\
135             #|| \sudo -nv &>/dev/null || \sudo -nv 2>&1 | \grep --quiet '^sudo:' # sudo is allowed with or without password
136         then
137             echo "trying sudo, enter your password:"
138             # 'sudo -u' seems better than 'sudo su' because the latter displayed problems with IOCTL (I/O-Control)
139             sudo -u "$next_user" "$tsu_cmd" && tsu_done="true"
140             #sudo su "$next_user" --command "$tsu_cmd" && tsu_done="true"
141         else
142             read -p "sudo might not be allowed for you ($USER), try anyway? [y/N] " try_sudo_anyway
143             [ "y" = "$try_sudo_anyway" ] && sudo -u "$next_user" "$tsu_cmd" && tsu_done="true"
144         fi
145         if ! "$tsu_done" && [ "$next_user" != "root" ]; then
146             echo "trying su 'root' to call sudo '$next_user', enter root's password:"
147             su "root" --command "sudo -u $next_user $tsu_cmd" && tsu_done="true"
148         fi
149     fi
150     if ! "$tsu_done"; then
151         echo "trying su '$next_user', enter $next_user's password:"
152         su "$next_user" --command "$tsu_cmd" && tsu_done="true"
153     fi
154     if ! "$tsu_done"; then
155         echo "trying su 'root' --command su '$next_user', enter root's password:"
156         su "root" --command "su $next_user --command '$tsu_cmd'" && tsu_done="true"
157     fi
158     $tsu_done || echo "sorry, tsu did not work in this case :-("
159     rm --force "$tsu_cmd"
160 }
161 complete -A user tsu # complete usernames
162 ```
163
164 * add code to your `.bashrc` (the one that will travel) to detect whether this is a „travelled rc session“ and make some configurations, i.e. make screen and vim use the travelled rc-files, and add the „trcbin“ directory to PATH
165     * I have this at the beginning of my `.bashrc`, but _after_ defaults for `EDITOR` and `INPUTRC` have been set
166
167 ```bash
168 if [ -n "$TRAVELRCDIR" ]; then # this is a travelled rc session
169     [ -f "$TRAVELRCDIR/.bashrc" ] && export TBASHRC="$TRAVELRCDIR/.bashrc"
170     [ -f "$TRAVELRCDIR/.inputrc" ] && export INPUTRC="$TRAVELRCDIR/.inputrc"
171     [ -f "$TRAVELRCDIR/.screenrc" ] && type -P screen &>/dev/null && alias screen="$(type -P screen) -c $TRAVELRCDIR/.screenrc -s trcbash"
172     [ -f "$TRAVELRCDIR/.tmux.conf" ] && type -P tmux &>/dev/null && alias ttmux="$(type -P tmux) -f $TRAVELRCDIR/.tmux.conf new-session 'trcbash'" # this alias does not work properly when named "tmux" (it fucked up other calls of tmux)
173     if [ -f "$TRAVELRCDIR/.vimrc" ] && type -P vim &>/dev/null; then
174         export EDITOR="$(type -P vim) -u $TRAVELRCDIR/.vimrc"
175         alias vim="$EDITOR"
176     fi
177     [ -d "$TRAVELRCDIR/trcbin" ] && export PATH="$TRAVELRCDIR/trcbin:$PATH" # add (prepend) trcbin to PATH
178 fi
179 ```
180
181 ## FAQ
182
183 * Can it fix more problems?
184     * yes, you can add more executables to PATH
185     * I had to execute scripts that unnecessarily used `clear`, which meant that I was not able to scroll up anymore, so I added a script to the `trcbin` directory with the name `clear`, but which did not do a real clear → problem solved (for me)
186 * Can it be used with zsh?
187     * most likely yes, just replace bash with zsh, but zsh might not be installed on the destination-system and then you're screwed; perhaps bash works with most of the zsh configurations, I can't tell
188 * Is it very efficient?
189     * probably not :-(
190     * I've tried to make it efficient, but I think in some cases it starts much more shells then necessary (i.e. `trcbash` in the `trcbin` directory)
191     * it might slow down ssh connections a few seconds, but for me having my rc-files with me outweighs this downside
192 * Does it work with tmux?
193     * not very well (see the ttmux alias above), but when tmux is started (or a new window is created), just type `trcbash` and you'll get a proper bash ;-)
194 * Is this project finished?
195     * I've been using it for around a year now and am still improving if necessary
196
197 ## License
198
199 CC-BY-NC-SA 4.0 https://creativecommons.org/licenses/by-nc-sa/4.0/
200