Compare commits

...

4 Commits

Author SHA1 Message Date
cryobry
3fc6b75ff0 Add command line options 2020-07-18 17:33:42 -04:00
cryobry
881dcaa964 First release version 2020-07-14 12:51:11 -04:00
cryobry
ac9f99116d Add README 2020-07-13 13:28:34 -04:00
cryobry
a99eefd7e5 Finish generalizing 2020-07-13 13:20:19 -04:00
5 changed files with 369 additions and 152 deletions

12
README.md Normal file
View File

@@ -0,0 +1,12 @@
Usage:
1. Configure user options in `install.sh`
2. Run `./install.sh` as your normal user
3. Confirm that user services are running and enabled: `systemctl --user status $name.service; systemctl --user status $name.timer`
4. If errors occur, check journal output: `journalctl -r -u $name.service`
Notes:
1. curlftpfs doesn't support file permissions, thus we must only use -r in rsync
2. curlftpfs doesn't support temporary files, thus the intermediate temp file step
3. I mount the share to /media (by design so that my file manager will display it if it is erroneously mounted) but it may be easier for users to mount the ftp share somewhere in $HOME to avoid permissions issues

View File

@@ -19,162 +19,368 @@
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFT
#
debug="off" # turn "on" for debugging
# IMPORTANT OPTIONS #
service_name="rsync-to-picture-frame" # use something specific and memorable
frame_address="192.168.1.100:2221" # you can find this on the frame, best to make static
source_dir="$HOME/Pictures/picture_frame" # source pictures/content from this directory
# Less important options
user="$(id -un)" # default run as current user
user_id="$(id -u)" # default run as current user
group_id="$(id -g)" # default run as current user
script_dir="$PWD" # where to copy the script to, default is $PWD (created automatically if missing)
mount_dir="/media/picture_frame_ftp" # directory to mount the ftp share to (created automatically if missing)
temp_dir="/tmp/picture_frame_ftp" # ftp does not support rsync temp files (created automatically if missing)
# Service settings
service_dir="$HOME/.config/systemd/user"
on_calendar="hourly" # how often to mount and sync
# END USER OPTIONS #
# if debug is on, echo additional output rsync-ftp-timer() {
debug() { [[ $debug == "on" ]] && echo "debug: $*"; }
check_and_install() { version="0.2"
#debug="true" # enable for debugging
[[ -v debug ]] && echo "Debugging on"
# IMPORTANT OPTIONS #
name="rsync-to-picture-frame" # use something specific and memorable
description="Mount picture frame ftp share and rsync syncthing picture_frame directory to it" # a short description
ftp_share="192.168.1.100:2221" # source share, best to make this a static address if you can
source_dir="$HOME/Pictures/picture_frame" # source files from this directory
# Less important options
user="$(id -un)" # default run as current user
user_id=$(id -u "$user")
group=$(id -gn "$user") # default use group of current user
group_id=$(id -g "$user")
script_dir="$PWD" # where to copy the script to, default is $PWD (created automatically if missing)
mount_dir="/media/$name" # directory to mount the ftp share to (created automatically if missing)
temp_dir="/tmp/name" # ftp does not support rsync temp files (created automatically if missing)
# Service settings
service_dir="$HOME/.config/systemd/user"
on_calendar="hourly" # how often to mount and sync
# END USER OPTIONS #
print_help_and_exit() {
debug "Running: ${FUNCNAME[0]}"
cat <<-'EOF'
USAGE
install.sh [[OPTION] [VALUE]]...
EXAMPLE
./install.sh \
-n "rsync-to-picture-frame" \
-d "Mount picture frame ftp share and rsync syncthing picture_frame directory to it" \
-f "192.168.1.102:2221" \
-s "$HOME/Pictures/picture_frame"
OPTIONS
--name, -n
Name of the service
--description, -d
Description of the service
--ftp-share, -f
The destination address of the ftp share to sync to (ex. 192.168.1.100:2221)
--source-dir, -s
The source directory to sync from
--user, -u
The user to run the service as (default: the current user)
--install-dir, -i
The location to install the script to (default: $PWD)
--mount-dir, -m
The location to mount the ftp share (default: /media/name)
--temp-dir, -t
The location of the temp directory (default: /tmp/name)
Note: FTP does not support rsync temp files so we must use a local temp dir
--service-dir
The location of the service directory (default: $HOME/.config/systemd/user)
--on-calendar
The systemd OnCalendar command (default: hourly)
--version, -v
Print this script version and exit
--debug
Print debug output
--help, -h
Print help dialog and exit
--uninstall
Completely uninstall the named service and remove associated directories
EOF
# Exit using passed exit code
[[ -z $1 ]] && exit 0 || exit "$1"
}
parse_input() {
debug "Running: ${FUNCNAME[0]}"
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
eval set -- "$_input"
while true; do
case "$1" in
--name|-n)
shift && name="$1"
;;
--description|-d)
shift && declare -g description="$1"
;;
--ftp-share|-f)
shift && declare -g ftp_share="$1"
;;
--source-dir|-s)
shift && declare -g source_dir="$1"
;;
--user|-u)
shift && \
declare -g user user_id group group_id && \
user="$1" && user_id=$(id -u "$user") && \
group=$(id -gn "$user") && group_id=$(id -g "$user")
;;
--install-dir|-i)
shift && declare -g script_dir="$1"
;;
--mount-dir|-m)
shift && declare -g mount_dir="$1"
;;
--temp-dir|-t)
shift && declare -g temp_dir="$1"
;;
--service-dir)
shift && declare -g service_dir="$1"
;;
--on-calendar)
shift && declare -g on_calendar="$1"
;;
--version|-v)
echo "Version: $version"
exit 0
;;
--debug)
echo "Debugging on"
debug="true"
;;
--help|-h)
print_help_and_exit 0
;;
--uninstall)
uninstall="true"
;;
--)
shift
break
;;
esac
shift
done
else
err "Incorrect option(s) provided"
print_help_and_exit 1
fi
}
err() { echo "Error: $*" >&2; }
debug() { [[ $debug == "true" ]] && echo "debug: $*"; }
# Happily check for a command and install its package
check_and_install() {
debug "Running: ${FUNCNAME[0]}" "$1"
if ! command -v "$1" > /dev/null 2>&1; then if ! command -v "$1" > /dev/null 2>&1; then
echo "Installing $1" echo "Installing $1"
[[ -f /etc/os-release ]] && source /etc/os-release [[ -f /etc/os-release ]] && source /etc/os-release
if [[ "${NAME,,}" =~ (fedora|centos) ]]; then if [[ "${NAME,,}" =~ (fedora|centos) ]]; then
debug "sudo dnf install -y $1" debug "sudo dnf install -y $1"
if ! sudo dnf install -y "$1"; then if ! sudo dnf install -y "$1"; then
echo "Could not install $1, exiting" err "Could not install $1, exiting"
exit 1 exit 1
fi fi
elif [[ "${NAME,,}" =~ (debian|ubuntu) ]]; then elif [[ "${NAME,,}" =~ (debian|ubuntu) ]]; then
debug "sudo apt install -y $1" debug "sudo apt install -y $1"
if ! sudo apt install -y "$1"; then if ! sudo apt install -y "$1"; then
echo "Could not install $1, exiting" err "Could not install $1, exiting"
exit 1 exit 1
fi fi
else else
echo "$1 must be installed" err "$1 must be installed"
exit 1 exit 1
fi fi
fi fi
return $? return $?
} }
# Happily make directories
mk_dir() { # Happily make directories
mk_dir() {
debug "Running: ${FUNCNAME[0]}" "$@"
local DIR
for DIR in "$@"; do for DIR in "$@"; do
if [[ ! -d "$DIR" ]]; then if [[ ! -d "$DIR" ]]; then
debug "mkdir -p $DIR" debug "mkdir -p $DIR"
if ! mkdir -p "$DIR"; then if ! mkdir -p "$DIR"; then
debug "sudo mkdir -p $DIR" debug "sudo mkdir -p $DIR"
if ! sudo mkdir -p "$DIR"; then if ! sudo mkdir -p "$DIR"; then
echo "sudo mkdir $DIR failed, exiting" err "sudo mkdir $DIR failed, exiting"
exit 1 exit 1
fi fi
fi fi
fi fi
done done
} }
# Happily chown directories as $user
chown_dir() { # Happily chown directories as $user|$group
chown_dir() {
debug "Running: ${FUNCNAME[0]}" "$@"
local DIR
local user="$1"
local group="$2"
shift 2
for DIR in "$@"; do for DIR in "$@"; do
debug "chown $user:$user -R $DIR" debug "chown $user:$group -R $DIR"
if ! chown "$user":"$user" -R "$DIR"; then if ! chown "$user":"$group" -R "$DIR"; then
debug "sudo chown $user:$user -R $DIR" debug "sudo chown $user:$group -R $DIR"
if ! sudo chown "$user":"$user" -R "$DIR"; then if ! sudo chown "$user":"$group" -R "$DIR"; then
echo "sudo chown on $DIR failed, exiting" err "sudo chown on $DIR failed, exiting"
exit 1 exit 1
fi fi
fi fi
done done
return $? }
}
# Happily make files executable
make_exec() { # Happily make files executable
make_exec() {
debug "Running: ${FUNCNAME[0]}" "$@"
local FILE
for FILE in "$@"; do for FILE in "$@"; do
debug "chmod a+x $FILE" debug "chmod a+x $FILE"
if ! chmod a+x "$FILE"; then if ! chmod a+x "$FILE"; then
debug "sudo chmod a+x $FILE" debug "sudo chmod a+x $FILE"
if ! sudo chmod a+x "$FILE"; then if ! sudo chmod a+x "$FILE"; then
echo "sudo chmod on $FILE failed, exiting" err "sudo chmod on $FILE failed, exiting"
exit 1 exit 1
fi fi
fi fi
done done
return $? }
}
# Happily copy files
cp_file() { # Happily copy files
cp_file() {
debug "Running: ${FUNCNAME[0]}" "$@"
if [[ ! -f "$1" ]]; then if [[ ! -f "$1" ]]; then
echo "$1 is missing" err "$1 is missing"
exit 1 exit 1
elif ! cp -af "$1" "$2"; then fi
echo "failed, retrying with sudo"
[[ -e "$2" ]] && rm_file_dir "$2"
if ! cp -af "$1" "$2"; then
err "failed, retrying with sudo"
debug "sudo cp -f $1 $2" debug "sudo cp -f $1 $2"
if ! sudo cp -af "$1" "$2"; then if ! sudo cp -af "$1" "$2"; then
echo "Copying script failed, exiting" err "Copying script failed, exiting"
exit 1 exit 1
fi fi
fi fi
} }
# Use sed to find ($1) and replace ($2) a string in a file ($3)
f_and_r() { # Happily remove a directory/file
rm_file_dir() {
debug "Running: ${FUNCNAME[0]}" "$@"
local OBJ
for OBJ in "$@"; do
if [[ -e "$OBJ" ]]; then
debug "rm -rf $OBJ"
if ! rm -rf "$OBJ"; then
err "failed, retrying with sudo"
debug "sudo rm -rf $OBJ"
if ! sudo rm -rf "$OBJ"; then
err "Could not remove $OBJ"
exit 1
fi
fi
fi
done
}
# Use sed to find ($1) and replace ($2) a string in a file ($3)
f_and_r() {
debug "Running: ${FUNCNAME[0]}" "$@"
debug "s#$1#$2#" "$3" debug "s#$1#$2#" "$3"
if ! sed -i "s#$1#$2#g" "$3"; then if ! sed -i "s#$1#$2#g" "$3"; then
exit 1 exit 1
fi fi
} }
main() {
_uninstall() {
if [[ -v name ]]; then
# Disable timer
debug "systemctl --user disable--now $name.timer"
systemctl --user disable --now "$name.timer"
# Remove service files
debug "rm_file_dir $service_dir/$name.timer $service_dir/$name.service"
rm_file_dir "$service_dir/$name.timer" "$service_dir/$name.service"
# Remove install script
debug "rm_file_dir $script_dir/$name.sh"
rm_file_dir "$script_dir/$name.sh"
# unmount drive
mountpoint -q -- "$mount_dir" && \
debug "fusermount -u $mount_dir" && \
fusermount -u "$mount_dir"
return 0
else
err "\$name must be set to uninstall"
return 1
fi
}
main() {
debug "Running: ${FUNCNAME[0]}" "$@"
# Parse input
parse_input "$@"
# Uninstall
[[ "$uninstall" == "true" ]] && _uninstall
# Install curlftps # Install curlftps
debug "check_and_install curlftpfs"
check_and_install "curlftpfs" check_and_install "curlftpfs"
# Disable existing timer # Disable existing timer
debug "systemctl --user disable --now $service_name.timer" debug "systemctl --user disable --now $name.timer"
systemctl --user disable --now "$service_name.timer" &> /dev/null systemctl --user disable --now "$name.timer" &> /dev/null
# Unmount existing ftp share # Unmount existing ftp share
mountpoint -q -- "$mount_dir" && fusermount -u "$mount_dir" mountpoint -q -- "$mount_dir" && \
debug "fusermount -u $mount_dir" && \
fusermount -u "$mount_dir"
# Create directories # Create directories
mk_dir "$source_dir" "$mount_dir" "$service_dir" "$script_dir" mk_dir "$source_dir" "$mount_dir" "$service_dir" "$script_dir"
chown_dir "$source_dir" "$mount_dir" "$service_dir" "$script_dir" chown_dir "$user" "$group" "$source_dir" "$mount_dir" "$service_dir" "$script_dir"
# Copy script file # Copy script file
cp_file "original.sh" "$script_dir/$service_name.sh" cp_file "original.sh" "$script_dir/$name.sh"
make_exec "$script_dir/$service_name.sh" make_exec "$script_dir/$name.sh"
f_and_r "{{mount_dir}}" "$mount_dir" "$script_dir/$service_name.sh" f_and_r "{{mount_dir}}" "$mount_dir" "$script_dir/$name.sh"
f_and_r "{{source_dir}}" "$source_dir" "$script_dir/$service_name.sh" f_and_r "{{source_dir}}" "$source_dir" "$script_dir/$name.sh"
f_and_r "{{frame_address}}" "$frame_address" "$script_dir/$service_name.sh" f_and_r "{{ftp_share}}" "$ftp_share" "$script_dir/$name.sh"
f_and_r "{{user_id}}" "$user_id" "$script_dir/$service_name.sh" f_and_r "{{user_id}}" "$user_id" "$script_dir/$name.sh"
f_and_r "{{group_id}}" "$group_id" "$script_dir/$service_name.sh" f_and_r "{{group_id}}" "$group_id" "$script_dir/$name.sh"
f_and_r "{{temp_dir}}" "$temp_dir" "$script_dir/$service_name.sh" f_and_r "{{temp_dir}}" "$temp_dir" "$script_dir/$name.sh"
# Copy service file # Copy service file
cp_file "original.service" "$service_dir/$service_name.service" cp_file "original.service" "$service_dir/$name.service"
f_and_r "{{path_to_script}}" "$script_dir/$service_name.sh" "$service_dir/$service_name.service" f_and_r "{{path_to_script}}" "$script_dir/$name.sh" "$service_dir/$name.service"
f_and_r "{{description}}" "$script_dir/$name.sh" "$service_dir/$name.service"
# Copy timer file # Copy timer file
cp_file "original.timer" "$service_dir/$service_name.timer" cp_file "original.timer" "$service_dir/$name.timer"
f_and_r "{{on_calendar}}" "$on_calendar" "$service_dir/$service_name.timer" f_and_r "{{on_calendar}}" "$on_calendar" "$service_dir/$name.timer"
f_and_r "{{description}}" "$description" "$service_dir/$name.timer"
# Enable timer # Run service and enable timer if successful
debug "systemctl --user daemon-reload" debug "systemctl --user daemon-reload"
systemctl --user daemon-reload systemctl --user daemon-reload
debug "systemctl --user enable --now $service_name.timer" debug "systemctl --user start $name.service"
systemctl --user enable --now "$service_name.timer" if systemctl --user start "$name.service"; then
debug "systemctl --user enable --now $name.timer"
systemctl --user enable --now "$name.timer"
else
err "systemctl --user start $name.service failed"
exit 1
fi
}
} }
main "$@"
exit $? # Allow this file to be executed directly if not being sourced
if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
rsync-ftp-timer
main "$@"
exit $?
fi

View File

@@ -1,5 +1,5 @@
[Unit] [Unit]
Description="Mount picture frame ftp and rsync photo directory" Description="{{description}}}}"
After=network-online.target After=network-online.target
Wants=network-online.target Wants=network-online.target

View File

@@ -1,6 +1,5 @@
#!/usr/bin/env bash #!/usr/bin/env bash
# This script will mount mom's photo frame ftp share locally # Use install.sh to edit settings
# Use install.sh to change settings
# if the mount already exists, unmount it # if the mount already exists, unmount it
mountpoint -q -- "{{mount_dir}}" && fusermount -u "{{mount_dir}}" mountpoint -q -- "{{mount_dir}}" && fusermount -u "{{mount_dir}}"
@@ -9,7 +8,7 @@ mountpoint -q -- "{{mount_dir}}" && fusermount -u "{{mount_dir}}"
[[ ! -d "{{temp_dir}}" ]] && mkdir -p "{{temp_dir}}" [[ ! -d "{{temp_dir}}" ]] && mkdir -p "{{temp_dir}}"
# Mount it and rsync over the photos # Mount it and rsync over the photos
if curlftpfs -o uid="{{user_id}}",gid="{{group_id}}" "{{frame_address}}" "{{mount_dir}}"; then if curlftpfs -o uid="{{user_id}}",gid="{{group_id}}" "{{ftp_share}}" "{{mount_dir}}"; then
if ! rsync -r --delete --quiet --temp-dir="{{temp_dir}}" "{{source_dir}}/" "{{mount_dir}}"; then if ! rsync -r --delete --quiet --temp-dir="{{temp_dir}}" "{{source_dir}}/" "{{mount_dir}}"; then
echo "rsync -r --delete --quiet {{source_dir}}/ {{mount_dir}} failed!" echo "rsync -r --delete --quiet {{source_dir}}/ {{mount_dir}} failed!"
exit 1 exit 1
@@ -19,12 +18,12 @@ if curlftpfs -o uid="{{user_id}}",gid="{{group_id}}" "{{frame_address}}" "{{moun
exit 1 exit 1
fi fi
else else
echo "Could not mount Mom's picture frame" echo "Could not mount ftp share"
if ping -c 1 "{{frame_address}}"; then if ping -c 1 "{{ftp_share}}"; then
echo "Ping OK, check the photo frame settings or the filesystem" echo "Ping OK, check the ftp device settings or the local filesystem permissions"
exit 1 exit 1
else else
echo "Could not ping the photo frame, is it on?" echo "Could not ping the device, is it on?"
exit 1 exit 1
fi fi
fi fi

View File

@@ -1,5 +1,5 @@
[Unit] [Unit]
Description="Transfer photos to picture frame hourly" Description="{{description}}"
[Timer] [Timer]
OnBootSec=1min OnBootSec=1min