#! /bin/sh # Author: # # Alberto Bursi # # Copyright: # # Alberto Bursi 2015-2021 # # License: # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This package is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # On Debian systems, the complete text of the GNU General # Public License version 3 can be found in `/usr/share/common-licenses/GPL-3'. # The general concept is making something like fs2ram, that you can find in Debian Unstable (as of 2016) # but smarter, easier and safer. # # The core idea comes from an init script called "transientlog" by Matteo Cortese # Available from here www.debian-administration.org/article/661/A_transient_/var/log # That page has been also saved and is available in the /doc/folder2ram folder for posterity. # # This modified version can do much more than just /var/log, runs with systemd, and can be shut down safely. # # "transientlog" borrowed quite a few of its logic from a script called "ramlog" by Jan Andrejkovic. # www.tremende.com/ramlog/index.htm #This script is HEAVILY commented, for the sake of easy understanding and mainteneance #If you edit it, please comment what you are doing VERSION="0.4.1" ############### USER INTERFACE FUNCTIONS ######################## print_usage() { echo "Welcome to folder2ram version $VERSION !" echo "folder2ram is a script-based utility that relocates the contents of a folder to RAM" echo "and on shutdown unmounts it safely synching the data back to the permanent storage." echo "" echo "There are four main components of folder2ram system:" echo "--the init script in /etc/init.d or the systemd service in /etc/folder2ram that calls this main script on boot and shutdown" echo "--the main script in /sbin/folder2ram" echo "--the configuration file in /etc/folder2ram/folder2ram.conf" echo "--the folders in /var/folder2ram, the bind-mounted folders" echo " they allow easy access to the original folder in permanent storage" echo " since if you mount folder A on folder B you lose access to folder B" echo " this trick allows access to B, allowing synching with the tmpfs at will" echo "" echo "for first startup use -configure action, edit the mount points as you wish, then -mountall" echo "" echo "list of actions (only one at a time):" echo "" echo "-enableinit" echo "::::::::::sets up an appropriate autostart/stop init script, does not start it" echo "" echo "-enablesystemd" echo "::::::::::sets up an appropriate autostart/stop systemd service, does not start it" echo "" echo "-disableinit" echo "::::::::::removes the autostart/stop init script and unmounts all mount points" echo "" echo "-disablesystemd" echo "::::::::::removes the autostart/stop systemd service and unmounts all mount points" echo "" echo "-safe-disableinit" echo "::::::::::removes the autostart/stop init script but unmounts only at shutdown (hence safely)" echo "::::::::::it also works if folder2ram is unistalled shortly afterwards" echo "" echo "-safe-disablesystemd" echo "::::::::::removes the autostart/stop systemd service but unmounts only at shutdown (hence safely)" echo "::::::::::it also works if folder2ram is unistalled shortly afterwards" echo "" echo "-status" echo "::::::::::print all mountpoints and their status (mounted or unmounted)" echo "" echo "-sync X" echo "::::::::::sync to disk the content of folder2ram's tmpfs folder number X (start counting from top entry in the config file)" echo "" echo "-syncall" echo "::::::::::sync to disk the content of folder2ram's tmpfs folders" echo "" echo "-mount /path/to/folder" echo "::::::::::folder2ram will mount this folder, if it's in the config file" echo "" echo "-umount /path/to/folder" echo "::::::::::folder2ram will unmount this folder, if it's in the config file" echo "" echo "-mountall" echo "::::::::::folder2ram will mount all folders in the config file" echo "" echo "-umountall" echo "::::::::::folder2ram will unmount all folders in the config file" echo "" echo "-configure" echo "::::::::::folder2ram will open the configuration file in a text editor" echo "" echo "-reset" echo "::::::::::restore default config file" echo "" echo "-clean" echo "::::::::::unmounts all folders then removes any autostart" echo "::::::::::WARNING: this might break programs that are using files in the tmpfs" echo "::::::::::if you have programs using the tmpfs please use -safe-disableinit or" echo "::::::::::-safe-disablesystemd, and then reboot the system" echo "" } #### END print_usage echo_with_timestamp() { echo "$(date --iso-8601=seconds) $*" } #################### FUNCTIONS THAT WRITE FILES AND CONFIGS ################################# write_initscript() { #writing a ridicolous amount of boilerplate initscript to ask for a simple line to be called on startup and shutdown cat <<'EOF' >"/etc/init.d/folder2ram" #! /bin/sh ### BEGIN INIT INFO # Provides: folder2ram # X-Start-Before: $syslog # X-Stop-After: $syslog # X-Interactive: yes # Required-Start: # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Keeps folders in RAM # Description: Moves the contents of user-defined folders to RAM during boot # and keeps it there until shutdown/reboot, when it # copies the contents back to permanent storage. ### END INIT INFO ##### WARNING::::AUTOGENERATED::::INIT.SCRIPT::::BY::::FOLDER2RAM PATH="/sbin:/bin:/usr/sbin:/usr/bin" Timeout=$(grep "^\s*#\s*TIMEOUT\s*=\s*[0-9]\+\s*m" /etc/folder2ram/folder2ram.conf | tail -n1 | sed -e 's/.*=//' -e 's/min/m/' -e 's/#.*//' -e 's/\s*//g') if [ "$Timeout" = '' ] ; then Timeout=2m fi timeout_monitor() { sleep "$Timeout" kill "$1" } # start the timeout monitor in # background and pass the PID: timeout_monitor "$$" & Timeout_monitor_pid=$! case "$1" in start) echo "Starting folder2ram" /sbin/folder2ram -mountall ;; stop) echo "Stopping folder2ram" /sbin/folder2ram -umountall ;; *) echo "Usage: {start|stop}" ;; esac # kill timeout monitor when terminating: kill "$Timeout_monitor_pid" exit 0 EOF #chmodding this script to make it only root/group-writable and root/group-executable chmod 774 "/etc/init.d/folder2ram" } #### END write_initscript write_cleanup_initscript() { cat <<'EOF' >"/etc/init.d/folder2ram_temporary" #! /bin/sh ### BEGIN INIT INFO # Provides: folder2ram # X-Start-Before: $syslog # X-Stop-After: $syslog # X-Interactive: yes # Required-Start: # Required-Stop: # Default-Start: 2 3 4 5 # Default-Stop: 0 1 6 # Short-Description: Keeps folders in RAM # Description: Moves the contents of user-defined folders to RAM during boot # and keeps it there until shutdown/reboot, when it # copies the contents back to permanent storage. # this component is needed for a safe shutdown. ### END INIT INFO ##### WARNING::::AUTOGENERATED::::INIT.SCRIPT::::BY::::FOLDER2RAM PATH="/sbin:/bin:/usr/sbin:/usr/bin" case "$1" in start) ## nothing ;; stop) echo "Stopping folder2ram for the last time" /sbin/folder2ram_cleaner ;; *) echo "Usage: {start|stop}" ;; esac exit EOF #chmodding this script to make it only root/group-writable and root/group-executable chmod 774 "/etc/init.d/folder2ram_temporary" } #### END write_cleanup_initscript write_systemd_startup_service() { if [ "$timeout_setting" != '' ]; then timeout_line="TimeoutSec=$timeout_setting" fi #writing a tiny amount of stuff to have systemd do the same thing as above. #clear symptom that systemd is master race. cat <"$systemd_startup_service_file" [Unit] Description=folder2ram systemd service After=local-fs.target After=blk-availability.service DefaultDependencies=no [Service] Type=oneshot RemainAfterExit=yes ExecStart=/sbin/folder2ram -mountall $timeout_line [Install] WantedBy=basic.target EOF #chmodding this file to make it only root/group-writable chmod 664 "$systemd_startup_service_file" } #### END write_systemd_service write_systemd_shutdown_service() { if [ "$timeout_setting" != '' ]; then timeout_line="TimeoutSec=$timeout_setting" fi #writing a tiny amount of stuff to have systemd do the same thing as above. #clear symptom that systemd is master race. cat <"$systemd_shutdown_service_file" [Unit] Description=folder2ram systemd service After=blk-availability.service BindsTo=blk-availability.service [Service] Type=oneshot RemainAfterExit=yes ExecStop=/sbin/folder2ram -umountall $timeout_line [Install] WantedBy=multi-user.target EOF #chmodding this file to make it only root/group-writable chmod 664 "$systemd_shutdown_service_file" } #### END write_systemd_service write_systemd_service_cleanup() { #asking to delete all the stuff we are leaving behind after we are done with the safe shutdown cat <<'EOF' >"$systemd_service_cleanup_file" [Unit] Description=temporary folder2ram systemd service [Service] Type=oneshot RemainAfterExit=yes ExecStart=/bin/true ExecStop=/sbin/folder2ram_cleaner TimeoutSec=30m [Install] WantedBy=multi-user.target EOF #chmodding this file to make it only root/group-writable chmod 664 "$systemd_service_cleanup_file" } #### END write_systemd_service_cleanup cleaner_script_generic() { # this function generates a generic cleaner script and customizes it for either systemd or initscripts systemd_or_init=$1 #possible values: init systemd cat <<'EOF' >"/sbin/folder2ram_cleaner" # CLEANER SCRIPT DESIGNED TO UNMOUNT ALL ON SHUTDOWN THEN DELETE ITSELF read_mount_point () { # this reads config file at a predetemined line and extracts mount point # $line_number must come from outside # blank lines and commented lines are ignored, so line_number refers only to actual mount points # a similar function can be used to extract additional options in the future line_number=$1 # remove blank and commented lines with sed, get variable form outside in awk, and use it to print line at $line_number # then remove trailing ///// with sed mount_point=$( sed '/^[[:space:]]*$/d' /etc/folder2ram_cleaner.conf | sed '/^#/d' | awk -v line="$line_number" 'NR == line {print $2}' | sed 's:/*$::' | grep -vE 'TYPE|OPTIONS|TIMEOUT' ) # checking if mount point variable is empty, and if it is returning a keyword if [ "x$mount_point" != "x" ]; then echo "$mount_point"; else echo "no_more_mount_points"; fi } #### END read_mount_point ############# MAIN PART ################## # initializing variables line_number=1 start_or_stop="stop" echo "will now $start_or_stop all mountpoints for the last time" # reading first mountpoint # calling another function (above) to fill the mount point at this line number mount_point=$(read_mount_point "$line_number") ####:::::#### BEGIN MAIN mount_umount_all UNTIL LOOP ####:::::#### until [ "$mount_point" = "no_more_mount_points" ] ; do ####:::::#### BEGIN PAYLOAD ####:::::#### #Setting up common variables DIR="$mount_point" LOCKFILE="/run/lock/$NAME.lock" TYPE="tmpfs" # DIRPERM is the bind mount to the directory in permanent storage # DIR is the directory we are working with #setting the place where all this stuff will be bind-mounted DIRPERM="/var/folder2ram$DIR" # unmounting stuff output_flag=0 # output_flag values: # 0 if all went well # 1 if it was unmounted already [ -f "$LOCKFILE" ] || output_flag=1 case $output_flag in 0) # Merge back to permanent storage with #rsync preserve-ACLs preserve-owner preserve-group preserve-extended-attributes quiet recursive links time archive --delete SOURCE ---> DESTINATION if rsync -o -g -A -X -q -r -l -t -a --delete "$DIR"'/' "$DIRPERM"; then # Success! rm "$LOCKFILE" umount -l "$DIR" umount -l "$DIRPERM" else echo "could not merge back to permanent storage" fi ;; 1) # already unmounted echo "$DIR already unmounted" ;; esac ####:::::#### END PAYLOAD ####:::::#### # increasing line number line_number=$((line_number+1)) # reading next mountpoint before next iteration in loop # calling another function (above) to fill the mount point at this line number mount_point=$(read_mount_point "$line_number") done ####:::::#### END OF mount_umount_all UNTIL LOOP ####:::::#### EOF #now adding customization to the script case $systemd_or_init in init) cat <<'EOF' >>"/sbin/folder2ram_cleaner" rm -f "/etc/init.d/folder2ram_cleaner" rm -f "/etc/folder2ram_cleaner.conf" rm -f "/sbin/folder2ram_cleaner" exit EOF ;; systemd) cat <>"/sbin/folder2ram_cleaner" systemctl disable folder2ram_cleaner.service rm -f "$systemd_service_cleanup_file" rm -f "/etc/folder2ram_cleaner.conf" rm -f "/sbin/folder2ram_cleaner" exit EOF ;; esac #chmodding this script to make it only root/group-writable and root/group-executable chmod 774 "/sbin/folder2ram_cleaner" } #### END cleaner_script_generic ################ CONFIGURATION FILE MANIPULATION FUNCTIONS ###################### write_config_file() { cat <"/etc/folder2ram/folder2ram.conf" ############################# #folder2ram main config file# ############################# # #Protip: to make /var/lock or /tmp available as ram filesystems, # it is preferable to set the variables RAMTMP, RAMLOCK # in /etc/default/tmpfs. ############################## # #Important: leave the # in front of these 3 settings: #TYPE: options available are "tmpfs" (for a ram folder) # #OPTIONS: mount option (will be passed as options to mount), if left blank "defaults" will be used # #TIMEOUT=2m #write here the timeout limit for folder2ram service activity # #(the time specified here must cover the mount/unmount time of all folders) # #if you are using systemd init, when you change this value you must run # #folder2ram -enablesystemd again to update the systemd service units with the new value # ############################# #Important: use 2 Tabs to separate "type" from "mount point" from "options", the script needs them to read correctly the configuration. # # #tmpfs /var/log EOF #chmodding the config to be root/group-writable chmod 664 "/etc/folder2ram/folder2ram.conf" } ## END write_config_file configure() { #if there is no folder, we create it [ -d "/etc/folder2ram" ] || mkdir "/etc/folder2ram" #if there is no config file it is generated from the template if [ ! -e "/etc/folder2ram/folder2ram.conf" ]; then # calling the function to write the config file write_config_file fi #will now ask for what text editor to use and then open it echo "will now open the configuration file with your favourite text editor" echo "write its name and press enter (nano, vim, gedit are the most common)" read -r editor "$editor" "/etc/folder2ram/folder2ram.conf" } #### END configure #################### UTILITY FUNCTIONS CALLED BY PRIMARY FUNCTIONS ############################# read_type() { # this reads config file at a predetemined line and extracts the filesystem option # $line_number must come from outside # blank lines and commented lines are ignored, so line_number refers only to actual mount points line_number=$1 # remove blank and commented lines with sed, get variable from outside in awk, and use it to print line at $line_number # then removing trailing //// with sed type=$(sed '/^[[:space:]]*$/d' /etc/folder2ram/folder2ram.conf | sed '/^#/d' | awk -v line="$line_number" 'NR == line {print $1}' | sed 's:/*$::' | grep -vE 'TYPE|OPTIONS|TIMEOUT') # checking if filesystem variable is empty, and if it is returning a keyword if [ "x$type" != "x" ]; then echo "$type" else echo "no_type" fi } #### END read_filesystem read_mount_point() { # this reads config file at a predetemined line and extracts mount point # $line_number must come from outside # blank lines and commented lines are ignored, so line_number refers only to actual mount points line_number=$1 # remove blank and commented lines with sed, get variable from outside in awk, and use it to print line at $line_number # then removing trailing //// with sed local mount_point="$(sed '/^[[:space:]]*$/d' /etc/folder2ram/folder2ram.conf | sed '/^#/d' | awk -v line="$line_number" 'NR == line {print $0}' | awk -F'\t\t' '{print $2}' | sed 's:/*$::' | tr -d '\t' | grep -vE 'TYPE|OPTIONS|TIMEOUT')" # checking if mount point variable is empty, and if it is returning a keyword if [ "x$mount_point" != "x" ]; then echo "$mount_point" else echo "no_more_mount_points" fi } #### END read_mount_point read_options() { # this reads config file at a predetemined line and extracts options # $line_number must come from outside # blank lines and commented lines are ignored, so line_number refers only to actual mount points line_number=$1 # remove blank and commented lines with sed, get variable from outside in awk, and use it to print line at $line_number # then removing trailing //// with sed options=$(sed '/^[[:space:]]*$/d' /etc/folder2ram/folder2ram.conf | sed '/^#/d' | awk -v line="$line_number" 'NR == line {print $0}' | awk -F'\t\t' '{print $3}' | sed 's:/*$::' | tr -d '\t') # checking if options variable is empty, and if it is returning a keyword if [ "x$options" != "x" ]; then echo "$options" else echo "defaults" fi } #### END read_options generate_mount_name() { # cleaning the mount point string to generate a name that won't break anything # basically turning /this/is/a/path + mount type into -this-is-a-path-mount_type #initializing variables mount_point=$1 # mount_type is hardcoded for now, will be set by something else if/when I implement more folder2ram options mount_type="tmpfs" # removing trailing slashes with sed then let awk handle this # field separator is set as "/" then the record separator is set as "-" , the loop prints one by one the fields # (because I could not find a better way to get the damn "-" to be inserted properly) # then disables the record separators and prints the variable "mount_type" coming from outside awk (see the -v option) #then replaces spaces in the names with "-" clean_mount_point=$(echo "$mount_point" | sed 's:/*$::' | awk -v mount_type="$mount_type" -F '/' '{ORS="-"; out=$1; for(i=1;i<=NF;i++){out=$i; print out}; ORS=""; print mount_type}' | tr ' ' '-') # generating the name, will look like this "folder2ram-this-is-a-path-mount_type" clean_name="f2r$clean_mount_point" #outputting the result echo "$clean_name" } #### END generate_mount_name generate_folder_with_same_permission() { newdir=$1 last_existing_parent_dir=$newdir #running loop to find out what's the last folder that exists in the path #while the folder entered is NOT found run while (! [ -d "$last_existing_parent_dir" ]); do #removing last folder from the path with sed because dirname complains if the parent folder does not exist last_existing_parent_dir=$(echo "$last_existing_parent_dir" | sed 's,/*[^/]\+/*$,,') done #making the folder and its path, not setting permissions now as mkdir -m does not set all permissions, chmod does. mkdir -p "$newdir" #chmodding recursively all folder path with same permissions as existing parent chmod -R --reference="$last_existing_parent_dir $last_existing_parent_dir" #chowning recursively all folder path with same permissions as existing parent chown -R --reference="$last_existing_parent_dir $last_existing_parent_dir" } #### END generate_folder_with_same_permission rsyslog_logrotate(){ #The following hack (forcing a logrotate) is done only for systemd's journald and rsyslog #because they are system services and it's too complex to move the service priority around journald #moving system logs from syslog while the files are open will mean logging will go on to the same log #files until they are rotated. #so we have to force the logrotate of this after the move is done logrotate_test=$( logrotate --version > /dev/null 2>&1 ; echo $? ) if [ "$journalctl_test" -eq 0 ]; then logrotate --force /etc/logrotate.conf fi } journald_logrotate(){ #The following hack (forcing a logrotate) is done only for systemd's journald and rsyslog #because they are system services and it's too complex to move the service priority around journald #moving journald's log folders can break the binary log files in /var/log/journal #so before we run the mount or unmount logic we force journald to logrotate #the old logs to a static file that can be moved safely journalctl_test=$( journalctl --version > /dev/null 2>&1 ; echo $? ) if [ "$journalctl_test" -eq 0 ]; then journalctl --rotate fi } ########################## FILE AND MOUNT FUNCTIONS ################################ mount_umount_all() { # initializing variables start_or_stop=$1 single_mount_point=$2 if [ -z "$single_mount_point" ]; then echo "will now $start_or_stop all mountpoints" else echo "will now $start_or_stop $single_mount_point" fi line_number=1 # reading first mountpoint # calling another function (above) to fill the type at this line number TYPE=$(read_type "$line_number") # calling another function (above) to fill the mount point at this line number mount_point="$(read_mount_point "$line_number")" # calling another function (above) to fill the options at this line number options=$(read_options "$line_number") ####:::::#### BEGIN MAIN mount_umount_all UNTIL LOOP ####:::::#### until [ "$mount_point" = "no_more_mount_points" ]; do if [ -z "$single_mount_point" ] || [ "$mount_point" = "$single_mount_point" ]; then echo "$start_or_stop" "$mount_point" ####:::::#### BEGIN PAYLOAD ####:::::#### #Setting up common variables NAME=$(generate_mount_name "$mount_point") DIR="$mount_point" LOCKFILE="/run/lock/$NAME.lock" #loading the mode of the parent directory MODE=$(env stat -c "%a " "$DIR") #echo $MODE # DIRPERM is the bind mount to the directory in permanent storage # DIR is the directory we are working with #setting the place where all this stuff will be bind-mounted DIRPERM="/var/folder2ram$DIR" #echo $DIRPERM #Deciding if mounting or unmounting stuff case "$start_or_stop" in ################# start) output_flag=0 # output_flag values: # 0 if all went well # 1 if the folder is already mounted # 2 if the folder does not exist [ -f "$LOCKFILE" ] && output_flag=1 # If DIR does not exist? [ -d "$DIR" ] || output_flag=2 # DIRPERM either does not exist (first invocation) # or is empty (left from previous invocation). # [ -d "$DIRPERM" ] || mkdir -p "$DIRPERM" || output_flag=2 #[ -d "$DIRPERM" ] || generate_folder_with_same_permission "$DIRPERM" || output_flag=2 #echo "done dirperm folder" case $output_flag in 0) #logrotate journald logs to preserve the current logged events journald_logrotate #switching mount operation depending on type of mount point case $TYPE in tmpfs) # Mount a tmpfs over DIR. # The mount will shadow the current contents of DIR. # So, before, make a bind mount so that looking into DIRPERM # we'll see the current contents of DIR, which # will not be available anymore as soon as we mount # a tmpfs over it. # # the --make-private option overrides the standard behavriour of systemd # which would be to propagate mounts through bindmounts. # Without it the mount-to-tmpfs command mounts both DIR and DIRPERM as tmpfs # which breaks everything. # mount -t tmpfs -o nosuid,noexec,nodev,mode=$MODE,size=$SIZE $NAME $DIR # original line, allows to accept options, a feature that I hope to add in the future. mount --bind --make-private "$DIR" "$DIRPERM" #here we generate/mount a tmpfs over it mount -t "tmpfs" -o "$options" "folder2ram" "$DIR" #echo "mount -t "tmpfs" -o "$options" "folder2ram" "$DIR"" #changing permissions, owner and groups to be the same as original #chmodding recursively all folder path with same permissions as existing parent chmod -R --reference="$DIRPERM" "$DIR" #chowning recursively all folder path with same permissions as existing parent chown -R --reference="$DIRPERM" "$DIR" # Populate the tmpfs with a simple cp if cp -rfp "$DIRPERM" -T "$DIR"; then # Success! touch "$LOCKFILE" else echo "copy files to $DIR failure, rolling back the mount" umount -l "$DIR" # Rollback the directory mangling umount -l "$DIRPERM" fi ;; esac #logrotate syslog logs to force the use of the new folder rsyslog_logrotate ;; 1) # already mounted echo "$DIR already mounted" ;; 2) # Something went wrong... # Rollback the mount umount -l "$DIR" # Rollback the directory mangling umount -l "$DIRPERM" ;; esac ;; ################# stop) output_flag=0 # output_flag values: # 0 if all went well # 1 if it was unmounted already [ -f "$LOCKFILE" ] || output_flag=1 case $output_flag in 0) #logrotate journald logs to preserve the current logged events journald_logrotate case $TYPE in tmpfs) # Merge back to permanent storage with #rsync preserve-ACLs preserve-owner preserve-group preserve-extended-attributes quiet recursive links time archive --delete #SOURCE ---> DESTINATION if rsync -o -g -A -X -r -l -t -a --delete "$DIR"'/' "$DIRPERM"; then # Success! rm "$LOCKFILE" umount -l "$DIR" umount -l "$DIRPERM" else echo "could not merge back to permanent storage" fi ;; esac #logrotate syslog logs to force the use of the new folder rsyslog_logrotate ;; 1) # already unmounted echo "$DIR already unmounted" ;; esac ;; ################# esac ####:::::#### END PAYLOAD ####:::::#### fi # increasing line number line_number=$((line_number + 1)) # reading next mountpoint before next iteration in loop # calling another function (above) to fill the type at this line number TYPE=$(read_type "$line_number") # calling another function (above) to fill the mount point at this line number mount_point=$(read_mount_point "$line_number") # calling another function (above) to fill the options at this line number options=$(read_options "$line_number") done ####:::::#### END OF mount_umount_all UNTIL LOOP ####:::::#### } ##### END mount_umount_all print_status() { #prints the status of all mounted folders # initializing variables line_number=1 #as we always start with the first line # calling another function (above) to fill the mount point at this line number mount_point=$(read_mount_point "$line_number") ####:::::#### BEGIN MAIN print_status UNTIL LOOP ####:::::#### until [ "$mount_point" = "no_more_mount_points" ]; do ####:::::#### BEGIN PAYLOAD ####:::::#### #generating lockfile mountname for this mount point NAME=$(generate_mount_name "$mount_point") LOCKFILE="/run/lock/$NAME.lock" #checking if this lockfile exists if [ -f "$LOCKFILE" ]; then echo "$mount_point is mounted" else echo "$mount_point is NOT mounted" fi ####:::::#### END PAYLOAD ####:::::#### # increasing line number line_number=$((line_number + 1)) # reading next mountpoint before next iteration in loop # calling another function (above) to fill the mount point at this line number mount_point=$(read_mount_point "$line_number") done ####:::::#### END MAIN print_status UNTIL LOOP ####:::::#### } #### END print_status sync_to_disk() { echo "-----------------------------------------" #used to allow selective sync of some folder choice="$1" # initializing variables line_number=1 if [ "$choice" = "0" ]; then echo_with_timestamp "will now sync all mountpoints" else echo_with_timestamp "will sync only mountpoint $choice" fi # reading first mountpoint # calling another function (above) to fill the type at this line number TYPE=$(read_type "$line_number") # calling another function (above) to fill the mount point at this line number mount_point=$(read_mount_point "$line_number") # calling another function (above) to fill the options at this line number options=$(read_options "$line_number") #logrotate journald logs to preserve the current logged events journald_logrotate ####:::::#### BEGIN MAIN sync_to_disk UNTIL LOOP ####:::::#### until [ "$mount_point" = "no_more_mount_points" ]; do if [ "$choice" = "0" ] || [ "$choice" = "$line_number" ]; then ####:::::#### BEGIN PAYLOAD ####:::::#### #Setting up common variables NAME=$(generate_mount_name "$mount_point") source="$mount_point" LOCKFILE="/run/lock/$NAME.lock" #SIZE=16M this can be interesting for later, for now it stays disabled #TYPE="tmpfs" #MODE=0755 # DIRPERM is the bind mount to the directory in permanent storage # DIR is the directory we are working with #setting the place where all this stuff will be bind-mounted case $TYPE in tmpfs) destination="/var/folder2ram$source" ;; esac # synching to disk output_flag=0 # output_flag values: # 0 if all went well # 1 if it was unmounted already [ -f "$LOCKFILE" ] || output_flag=1 case $output_flag in 0) # Merge back to permanent storage with #rsync preserve-ACLs preserve-owner preserve-group preserve-extended-attributes quiet recursive links time archive --delete SOURCE ---> DESTINATION if rsync -o -g -A -X -q -r -l -t -a --delete "$source"'/' "$destination"; then echo_with_timestamp "sync of $source successful!" else echo_with_timestamp "could not sync $source" fi ;; 1) # already unmounted echo_with_timestamp "$source unmounted, cannot comply" ;; esac ####:::::#### END PAYLOAD ####:::::#### fi # increasing line number line_number=$((line_number + 1)) # reading next mountpoint before next iteration in loop # calling another function (above) to fill the type at this line number TYPE=$(read_type "$line_number") # calling another function (above) to fill the mount point at this line number mount_point=$(read_mount_point "$line_number") # calling another function (above) to fill the options at this line number options=$(read_options "$line_number") done ####:::::#### END MAIN sync_to_disk UNTIL LOOP ####:::::#### if [ "$choice" -ge "$line_number" ] && [ "$choice" != "0" ]; then echo_with_timestamp "mountpoint $choice does not exist" fi } #### END sync_to_disk ########################## AUTOSTART FUNCTIONS ################################ setup_autostart() { # initializing variables setup=$1 #detecting where is the best place for systemd stuff #as debian likes to place it in /lib/systemd/system/ #but opensuse likes to place it in /usr/lib/systemd/system/ and has no /lib/systemd/system/ #so we detect where the "systemd" binary file is located, if it is in /lib/systemd we use that #if it is in /usr/lib/systemd we use that. if [ -f "/lib/systemd/systemd" ]; then systemd_startup_service_file="/lib/systemd/system/folder2ram_startup.service" systemd_shutdown_service_file="/lib/systemd/system/folder2ram_shutdown.service" systemd_service_cleanup_file="/lib/systemd/system/folder2ram_cleaner.service" fi if [ -f "/usr/lib/systemd/systemd" ]; then systemd_startup_service_file="/usr/lib/systemd/system/folder2ram_startup.service" systemd_shutdown_service_file="/usr/lib/systemd/system/folder2ram_shutdown.service" systemd_service_cleanup_file="/usr/lib/systemd/system/folder2ram_cleaner.service" fi #reading timeout setting timeout_setting=$(grep "^\s*#\s*TIMEOUT\s*=\s*[0-9]\+\s*m" /etc/folder2ram/folder2ram.conf | tail -n1 | sed -e 's/.*=//' -e 's/min/m/' -e 's/#.*//' -e 's/\s*//g') # Deciding if we want init script or systemd module # Also deciding if we want to enable or disable NOW, or if we want to enable/disable on reboot. case "$setup" in init_install) #calling the function that writes the initscript and sets permissions write_initscript #activating it in init insserv folder2ram ;; init_remove) #stopping everything first mount_umount_all stop # to remove from init script insserv -r folder2ram #to delete the script rm -f "/etc/init.d/folder2ram" ;; init_safe_remove) # to remove from init script insserv -r folder2ram #to delete the script rm -f "/etc/init.d/folder2ram" #making dedicated cleanup script by calling function first #asking for the init-specific self-destruct and sets permissions cleaner_script_generic init # now we clone folder2ram's config file cp "/etc/folder2ram/folder2ram.conf" "/etc/folder2ram_cleaner.conf" # now we need to place a one-shot initscript and activate it and set its permissions write_cleanup_initscript #activating it in init insserv folder2ram_temporary ;; systemd_install) #calling the function that writes the systemd service file and sets permissions write_systemd_startup_service # enabling the service, will be started on reboot systemctl enable "$systemd_startup_service_file" #calling the function that writes the systemd service file and sets permissions write_systemd_shutdown_service # enabling the service, will be started on reboot systemctl enable "$systemd_shutdown_service_file" ;; systemd_remove) #stopping everything first mount_umount_all stop #disabling the service systemctl disable "$systemd_startup_service_file" systemctl disable "$systemd_shutdown_service_file" ;; systemd_safe_remove) #disabling the service systemctl disable "$systemd_startup_service_file" systemctl disable "$systemd_shutdown_service_file" #now we pull the same trick as with the initscript above, but modified to work with systemd #making dedicated cleanup script by calling function first #adding the systemd-specific self-destruct and sets permissions cleaner_script_generic systemd # now we clone folder2ram's config file cp "/etc/folder2ram/folder2ram.conf" "/etc/folder2ram_cleaner.conf" #making a new service pointing to a the cleaner script calling this function and sets permissions write_systemd_service_cleanup # enabling the service systemctl enable "$systemd_service_cleanup_file" ;; esac } ####END setup_autostart clean() { #this stops folder2ram and removes autostarts #it asks other functions to do the leg work #making a few checks to run cleaners or unmounter only if there is something to clean #as running it twice would unmount folders three times giving confusing error output if [ -f "/etc/init.d/folder2ram" ]; then setup_autostart init_remove else if [ -f "$systemd_startup_service_file" ] || [ -f "$systemd_shutdown_service_file" ]; then setup_autostart systemd_remove else mount_umount_all stop fi fi } #### END clean reset_config() { echo "will revert all changes of folder2ram.conf" echo "you sure you want to do that (y or n)" read -r choice case "$choice" in Y | y | yes | Yes | YES) #if there is no folder, we create it [ -d "/etc/folder2ram" ] || mkdir "/etc/folder2ram" #removing current config and making a new one rm -f "/etc/folder2ram/folder2ram.conf" # calling the function to write the config file write_config_file ;; N | n | no | No | NO) echo "ok, nevermind then" exit ;; *) echo "please write y for yes or n for no" ;; esac } #### END reset_config #########################END MAIN FUNCTIONS########################### #####################--START MAIN PROGRAM--################################ action="$1" #doing a root check, because folder2ram must be run as root for obvious reasons if [ "$(id -u)" -eq 0 ]; then echo else echo "you must run folder2ram as root" exit fi #checking that crucial tools are installed for tool in cp rsync mount umount rm chown chmod mkdir sed tr; do tool_test=$( $tool --version > /dev/null 2>&1 ; echo $? ) if [ "$tool_test" -ne 0 ]; then echo ERROR: $tool tool not found, please install it. Quitting exit 1 fi done #awk is different so it gets its own checker tool_test=$( awk -W version > /dev/null 2>&1 ; echo $? ) if [ "$tool_test" -ne 0 ]; then echo ERROR: awk tool not found, please install it. Quitting exit 1 fi case "$action" in -status) print_status ;; -mountall) mount_umount_all start ;; -umountall) mount_umount_all stop ;; -mount) mount_umount_all start $2 ;; -umount) mount_umount_all stop $2 ;; -enableinit) setup_autostart init_install ;; -enablesystemd) setup_autostart systemd_install echo "systemd services enabled but not started, it is recommended to reboot for a cleaner transition to tmpfs folders" echo "otherwise you can start them now (both services are needed) with " echo "systemctl start folder2ram_startup.service" echo "systemctl start folder2ram_shutdown.service" ;; -disableinit) setup_autostart init_remove ;; -disablesystemd) setup_autostart systemd_remove ;; -safe-disableinit) setup_autostart init_safe_remove ;; -safe-disablesystemd) setup_autostart systemd_safe_remove ;; -configure) configure ;; -reset) reset_config ;; -sync) sync_to_disk "$2" ;; -syncall) sync_to_disk "0" ;; -clean) clean ;; *) print_usage ;; esac exit