install.sh 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386
  1. #!/usr/bin/env bash
  2. # This script will install a systemd service and timer to mount a remote ftp share, rsync a directory to it, and then unmount the share
  3. # I'm using this to sync photos to a digital picture frame
  4. #
  5. # Copyright (c) 2020 Bryan Roessler <bryanroessler@gmail.com>
  6. # Permission is hereby granted, free of charge, to any person obtaining a copy
  7. # of this software and associated documentation files (the "Software"), to deal
  8. # in the Software without restriction, including without limitation the rights
  9. # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  10. # copies of the Software, and to permit persons to whom the Software is
  11. # furnished to do so, subject to the following conditions:
  12. #
  13. # The above copyright notice and this permission notice shall be included in all
  14. # copies or substantial portions of the Software.
  15. #
  16. # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  17. # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  18. # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  19. # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  20. # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  21. # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
  22. # SOFT
  23. rsync-ftp-timer() {
  24. version="0.2"
  25. #debug="true" # enable for debugging
  26. [[ -v debug ]] && echo "Debugging on"
  27. # IMPORTANT OPTIONS #
  28. name="rsync-to-picture-frame" # use something specific and memorable
  29. description="Mount picture frame ftp share and rsync syncthing picture_frame directory to it" # a short description
  30. ftp_share="192.168.1.100:2221" # source share, best to make this a static address if you can
  31. source_dir="$HOME/Pictures/picture_frame" # source files from this directory
  32. # Less important options
  33. user="$(id -un)" # default run as current user
  34. user_id=$(id -u "$user")
  35. group=$(id -gn "$user") # default use group of current user
  36. group_id=$(id -g "$user")
  37. script_dir="$PWD" # where to copy the script to, default is $PWD (created automatically if missing)
  38. mount_dir="/media/$name" # directory to mount the ftp share to (created automatically if missing)
  39. temp_dir="/tmp/name" # ftp does not support rsync temp files (created automatically if missing)
  40. # Service settings
  41. service_dir="$HOME/.config/systemd/user"
  42. on_calendar="hourly" # how often to mount and sync
  43. # END USER OPTIONS #
  44. print_help_and_exit() {
  45. debug "Running: ${FUNCNAME[0]}"
  46. cat <<-'EOF'
  47. USAGE
  48. install.sh [[OPTION] [VALUE]]...
  49. EXAMPLE
  50. ./install.sh \
  51. -n "rsync-to-picture-frame" \
  52. -d "Mount picture frame ftp share and rsync syncthing picture_frame directory to it" \
  53. -f "192.168.1.102:2221" \
  54. -s "$HOME/Pictures/picture_frame"
  55. OPTIONS
  56. --name, -n
  57. Name of the service
  58. --description, -d
  59. Description of the service
  60. --ftp-share, -f
  61. The destination address of the ftp share to sync to (ex. 192.168.1.100:2221)
  62. --source-dir, -s
  63. The source directory to sync from
  64. --user, -u
  65. The user to run the service as (default: the current user)
  66. --install-dir, -i
  67. The location to install the script to (default: $PWD)
  68. --mount-dir, -m
  69. The location to mount the ftp share (default: /media/name)
  70. --temp-dir, -t
  71. The location of the temp directory (default: /tmp/name)
  72. Note: FTP does not support rsync temp files so we must use a local temp dir
  73. --service-dir
  74. The location of the service directory (default: $HOME/.config/systemd/user)
  75. --on-calendar
  76. The systemd OnCalendar command (default: hourly)
  77. --version, -v
  78. Print this script version and exit
  79. --debug
  80. Print debug output
  81. --help, -h
  82. Print help dialog and exit
  83. --uninstall
  84. Completely uninstall the named service and remove associated directories
  85. EOF
  86. # Exit using passed exit code
  87. [[ -z $1 ]] && exit 0 || exit "$1"
  88. }
  89. parse_input() {
  90. debug "Running: ${FUNCNAME[0]}"
  91. if _input=$(getopt -o +n:d:f:s:u:i:m:t:vhu -l name:,description:,ftp-share:,source-dir:,user:,install-dir:,mount-dir:,temp-dir:,service-dir:,on-calendar:,version,debug,help,uninstall -- "$@"); then
  92. eval set -- "$_input"
  93. while true; do
  94. case "$1" in
  95. --name|-n)
  96. shift && name="$1"
  97. ;;
  98. --description|-d)
  99. shift && declare -g description="$1"
  100. ;;
  101. --ftp-share|-f)
  102. shift && declare -g ftp_share="$1"
  103. ;;
  104. --source-dir|-s)
  105. shift && declare -g source_dir="$1"
  106. ;;
  107. --user|-u)
  108. shift && \
  109. declare -g user user_id group group_id && \
  110. user="$1" && user_id=$(id -u "$user") && \
  111. group=$(id -gn "$user") && group_id=$(id -g "$user")
  112. ;;
  113. --install-dir|-i)
  114. shift && declare -g script_dir="$1"
  115. ;;
  116. --mount-dir|-m)
  117. shift && declare -g mount_dir="$1"
  118. ;;
  119. --temp-dir|-t)
  120. shift && declare -g temp_dir="$1"
  121. ;;
  122. --service-dir)
  123. shift && declare -g service_dir="$1"
  124. ;;
  125. --on-calendar)
  126. shift && declare -g on_calendar="$1"
  127. ;;
  128. --version|-v)
  129. echo "Version: $version"
  130. exit 0
  131. ;;
  132. --debug)
  133. echo "Debugging on"
  134. debug="true"
  135. ;;
  136. --help|-h)
  137. print_help_and_exit 0
  138. ;;
  139. --uninstall)
  140. uninstall="true"
  141. ;;
  142. --)
  143. shift
  144. break
  145. ;;
  146. esac
  147. shift
  148. done
  149. else
  150. err "Incorrect option(s) provided"
  151. print_help_and_exit 1
  152. fi
  153. }
  154. err() { echo "Error: $*" >&2; }
  155. debug() { [[ $debug == "true" ]] && echo "debug: $*"; }
  156. # Happily check for a command and install its package
  157. check_and_install() {
  158. debug "Running: ${FUNCNAME[0]}" "$1"
  159. if ! command -v "$1" > /dev/null 2>&1; then
  160. echo "Installing $1"
  161. [[ -f /etc/os-release ]] && source /etc/os-release
  162. if [[ "${NAME,,}" =~ (fedora|centos) ]]; then
  163. debug "sudo dnf install -y $1"
  164. if ! sudo dnf install -y "$1"; then
  165. err "Could not install $1, exiting"
  166. exit 1
  167. fi
  168. elif [[ "${NAME,,}" =~ (debian|ubuntu) ]]; then
  169. debug "sudo apt install -y $1"
  170. if ! sudo apt install -y "$1"; then
  171. err "Could not install $1, exiting"
  172. exit 1
  173. fi
  174. else
  175. err "$1 must be installed"
  176. exit 1
  177. fi
  178. fi
  179. return $?
  180. }
  181. # Happily make directories
  182. mk_dir() {
  183. debug "Running: ${FUNCNAME[0]}" "$@"
  184. local DIR
  185. for DIR in "$@"; do
  186. if [[ ! -d "$DIR" ]]; then
  187. debug "mkdir -p $DIR"
  188. if ! mkdir -p "$DIR"; then
  189. debug "sudo mkdir -p $DIR"
  190. if ! sudo mkdir -p "$DIR"; then
  191. err "sudo mkdir $DIR failed, exiting"
  192. exit 1
  193. fi
  194. fi
  195. fi
  196. done
  197. }
  198. # Happily chown directories as $user|$group
  199. chown_dir() {
  200. debug "Running: ${FUNCNAME[0]}" "$@"
  201. local DIR
  202. local user="$1"
  203. local group="$2"
  204. shift 2
  205. for DIR in "$@"; do
  206. debug "chown $user:$group -R $DIR"
  207. if ! chown "$user":"$group" -R "$DIR"; then
  208. debug "sudo chown $user:$group -R $DIR"
  209. if ! sudo chown "$user":"$group" -R "$DIR"; then
  210. err "sudo chown on $DIR failed, exiting"
  211. exit 1
  212. fi
  213. fi
  214. done
  215. }
  216. # Happily make files executable
  217. make_exec() {
  218. debug "Running: ${FUNCNAME[0]}" "$@"
  219. local FILE
  220. for FILE in "$@"; do
  221. debug "chmod a+x $FILE"
  222. if ! chmod a+x "$FILE"; then
  223. debug "sudo chmod a+x $FILE"
  224. if ! sudo chmod a+x "$FILE"; then
  225. err "sudo chmod on $FILE failed, exiting"
  226. exit 1
  227. fi
  228. fi
  229. done
  230. }
  231. # Happily copy files
  232. cp_file() {
  233. debug "Running: ${FUNCNAME[0]}" "$@"
  234. if [[ ! -f "$1" ]]; then
  235. err "$1 is missing"
  236. exit 1
  237. fi
  238. [[ -e "$2" ]] && rm_file_dir "$2"
  239. if ! cp -af "$1" "$2"; then
  240. err "failed, retrying with sudo"
  241. debug "sudo cp -f $1 $2"
  242. if ! sudo cp -af "$1" "$2"; then
  243. err "Copying script failed, exiting"
  244. exit 1
  245. fi
  246. fi
  247. }
  248. # Happily remove a directory/file
  249. rm_file_dir() {
  250. debug "Running: ${FUNCNAME[0]}" "$@"
  251. local OBJ
  252. for OBJ in "$@"; do
  253. if [[ -e "$OBJ" ]]; then
  254. debug "rm -rf $OBJ"
  255. if ! rm -rf "$OBJ"; then
  256. err "failed, retrying with sudo"
  257. debug "sudo rm -rf $OBJ"
  258. if ! sudo rm -rf "$OBJ"; then
  259. err "Could not remove $OBJ"
  260. exit 1
  261. fi
  262. fi
  263. fi
  264. done
  265. }
  266. # Use sed to find ($1) and replace ($2) a string in a file ($3)
  267. f_and_r() {
  268. debug "Running: ${FUNCNAME[0]}" "$@"
  269. debug "s#$1#$2#" "$3"
  270. if ! sed -i "s#$1#$2#g" "$3"; then
  271. exit 1
  272. fi
  273. }
  274. _uninstall() {
  275. if [[ -v name ]]; then
  276. # Disable timer
  277. debug "systemctl --user disable--now $name.timer"
  278. systemctl --user disable --now "$name.timer"
  279. # Remove service files
  280. debug "rm_file_dir $service_dir/$name.timer $service_dir/$name.service"
  281. rm_file_dir "$service_dir/$name.timer" "$service_dir/$name.service"
  282. # Remove install script
  283. debug "rm_file_dir $script_dir/$name.sh"
  284. rm_file_dir "$script_dir/$name.sh"
  285. # unmount drive
  286. mountpoint -q -- "$mount_dir" && \
  287. debug "fusermount -u $mount_dir" && \
  288. fusermount -u "$mount_dir"
  289. return 0
  290. else
  291. err "\$name must be set to uninstall"
  292. return 1
  293. fi
  294. }
  295. main() {
  296. debug "Running: ${FUNCNAME[0]}" "$@"
  297. # Parse input
  298. parse_input "$@"
  299. # Uninstall
  300. [[ "$uninstall" == "true" ]] && _uninstall
  301. # Install curlftps
  302. check_and_install "curlftpfs"
  303. # Disable existing timer
  304. debug "systemctl --user disable --now $name.timer"
  305. systemctl --user disable --now "$name.timer" &> /dev/null
  306. # Unmount existing ftp share
  307. mountpoint -q -- "$mount_dir" && \
  308. debug "fusermount -u $mount_dir" && \
  309. fusermount -u "$mount_dir"
  310. # Create directories
  311. mk_dir "$source_dir" "$mount_dir" "$service_dir" "$script_dir"
  312. chown_dir "$user" "$group" "$source_dir" "$mount_dir" "$service_dir" "$script_dir"
  313. # Copy script file
  314. cp_file "original.sh" "$script_dir/$name.sh"
  315. make_exec "$script_dir/$name.sh"
  316. f_and_r "{{mount_dir}}" "$mount_dir" "$script_dir/$name.sh"
  317. f_and_r "{{source_dir}}" "$source_dir" "$script_dir/$name.sh"
  318. f_and_r "{{ftp_share}}" "$ftp_share" "$script_dir/$name.sh"
  319. f_and_r "{{user_id}}" "$user_id" "$script_dir/$name.sh"
  320. f_and_r "{{group_id}}" "$group_id" "$script_dir/$name.sh"
  321. f_and_r "{{temp_dir}}" "$temp_dir" "$script_dir/$name.sh"
  322. # Copy service file
  323. cp_file "original.service" "$service_dir/$name.service"
  324. f_and_r "{{path_to_script}}" "$script_dir/$name.sh" "$service_dir/$name.service"
  325. f_and_r "{{description}}" "$script_dir/$name.sh" "$service_dir/$name.service"
  326. # Copy timer file
  327. cp_file "original.timer" "$service_dir/$name.timer"
  328. f_and_r "{{on_calendar}}" "$on_calendar" "$service_dir/$name.timer"
  329. f_and_r "{{description}}" "$description" "$service_dir/$name.timer"
  330. # Run service and enable timer if successful
  331. debug "systemctl --user daemon-reload"
  332. systemctl --user daemon-reload
  333. debug "systemctl --user start $name.service"
  334. if systemctl --user start "$name.service"; then
  335. debug "systemctl --user enable --now $name.timer"
  336. systemctl --user enable --now "$name.timer"
  337. else
  338. err "systemctl --user start $name.service failed"
  339. exit 1
  340. fi
  341. }
  342. }
  343. # Allow this file to be executed directly if not being sourced
  344. if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  345. rsync-ftp-timer
  346. main "$@"
  347. exit $?
  348. fi