Move /sbin to /bin

As more users would have /bin than /sbin in their PATH
This commit is contained in:
Erik Westrup
2022-02-07 18:22:33 +01:00
parent 54e2d17e23
commit 645df1a0d4
15 changed files with 19 additions and 19 deletions

24
bin/cron_mail Executable file
View File

@@ -0,0 +1,24 @@
#!/usr/bin/env sh
# vi: ft=sh
#
# To be called by a cron job as a wrapper that sends stdour and stderr via the mail program.
# Why? Because of FreeBSD the system cron uses sendmail, and I want to use ssmtp.
# Make your crontab files like:
#SHELL=/bin/sh
#PATH=/etc:/bin:/sbin:/usr/bin:/usr/sbin:/usr/local/sbin:$INSTALL_PREFIX/bin
#@daily root cron_mail freebsd-update cron
mail_target=root
scriptname=${0##*/}
if [ $# -eq 0 ]; then
echo "No program to run given!" >&2
exit 1
fi
cmd="$*"
body=$(eval "$cmd" 2>&1)
if [ -n "$body" ];then
echo "$body" | mail -s "${scriptname}: ${cmd}" $mail_target
fi

View File

@@ -0,0 +1,19 @@
#!/bin/bash
systemctl is-active dbus.service >/dev/null 2>&1 || exit 0
systemctl is-active NetworkManager.service >/dev/null 2>&1 || exit 0
metered_status=$(dbus-send --system --print-reply=literal \
--system --dest=org.freedesktop.NetworkManager \
/org/freedesktop/NetworkManager \
org.freedesktop.DBus.Properties.Get \
string:org.freedesktop.NetworkManager string:Metered \
| grep -o ".$")
if [[ $metered_status =~ (1|3) ]]; then
echo Current connection is metered
exit 1
else
exit 0
fi

103
bin/restic_backup.sh Normal file
View File

@@ -0,0 +1,103 @@
#!/usr/bin/env bash
# Make a backup with restic to Backblaze B2.
#
# This script is typically run (as root user) either like:
# - from restic service/timer: $PREFIX/etc/systemd/system/restic-backup.{service,timer}
# - from a cronjob: $PREFIX/etc/cron.d/restic
# - manually by a user. For it to work, the environment variables must be set in the shell where this script is executed
# $ source $PREFIX/etc/default.env
# $ restic_backup.sh
# Exit on error, unset var, pipe failure
set -euo pipefail
# Clean up lock if we are killed.
# If killed by systemd, like $(systemctl stop restic), then it kills the whole cgroup and all it's subprocesses.
# However if we kill this script ourselves, we need this trap that kills all subprocesses manually.
exit_hook() {
echo "In exit_hook(), being killed" >&2
jobs -p | xargs kill
restic unlock
}
trap exit_hook INT TERM
# Assert that all needed environment variables are set.
# TODO in future if this grows, move this to a restic_lib.sh
assert_envvars() {
local varnames=("$@")
for varname in "${varnames[@]}"; do
if [ -z ${!varname+x} ]; then
printf "%s must be set for this script to work.\n\nDid you forget to source a /etc/restic/*.env profile in the current shell before executing this script?\n" "$varname" >&2
exit 1
fi
done
}
assert_envvars \
B2_ACCOUNT_ID B2_ACCOUNT_KEY B2_CONNECTIONS \
RESTIC_BACKUP_PATHS RESTIC_BACKUP_TAG \
RESTIC_BACKUP_EXCLUDE_FILE RESTIC_BACKUP_EXTRA_ARGS RESTIC_PASSWORD_FILE RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL \
RESTIC_RETENTION_DAYS RESTIC_RETENTION_MONTHS RESTIC_RETENTION_WEEKS RESTIC_RETENTION_YEARS
# Convert to arrays, as arrays should be used to build command lines. See https://github.com/koalaman/shellcheck/wiki/SC2086
IFS=':' read -ra backup_paths <<< "$RESTIC_BACKUP_PATHS"
IFS=' ' read -ra extra_args <<< "$RESTIC_BACKUP_EXTRA_ARGS"
# Set up exclude files: global + path-specific ones
# NOTE that restic will fail the backup if not all listed --exclude-files exist. Thus we should only list them if they are really all available.
## Global backup configuration.
exclusion_args=(--exclude-file "$RESTIC_BACKUP_EXCLUDE_FILE")
## Self-contained backup exclusion files per backup path. E.g. having an USB disk at /mnt/media in RESTIC_BACKUP_PATHS,
# then a file /mnt/media/.backup_exclude.txt will automatically be detected and used:
for backup_path in "${backup_paths[@]}"; do
if [ -f "$backup_path/.backup_exclude.txt" ]; then
exclusion_args=("${exclusion_args[@]}" --exclude-file "$backup_path/.backup_exclude.txt")
fi
done
# NOTE start all commands in background and wait for them to finish.
# Reason: bash ignores any signals while child process is executing and thus the trap exit hook is not triggered.
# However if put in subprocesses, wait(1) waits until the process finishes OR signal is received.
# Reference: https://unix.stackexchange.com/questions/146756/forward-sigterm-to-child-in-bash
# Remove locks from other stale processes to keep the automated backup running.
restic unlock &
wait $!
# Do the backup!
# See restic-backup(1) or http://restic.readthedocs.io/en/latest/040_backup.html
# --one-file-system makes sure we only backup exactly those mounted file systems specified in $RESTIC_BACKUP_PATHS, and thus not directories like /dev, /sys etc.
# --tag lets us reference these backups later when doing restic-forget.
restic backup \
--verbose="$RESTIC_VERBOSITY_LEVEL" \
--one-file-system \
--tag "$RESTIC_BACKUP_TAG" \
--option b2.connections="$B2_CONNECTIONS" \
"${exclusion_args[@]}" \
"${extra_args[@]}" \
"${backup_paths[@]}" &
wait $!
# Dereference and delete/prune old backups.
# See restic-forget(1) or http://restic.readthedocs.io/en/latest/060_forget.html
# --group-by only the tag and path, and not by hostname. This is because I create a B2 Bucket per host, and if this hostname accidentially change some time, there would now be multiple backup sets.
restic forget \
--verbose="$RESTIC_VERBOSITY_LEVEL" \
--tag "$RESTIC_BACKUP_TAG" \
--option b2.connections="$B2_CONNECTIONS" \
--prune \
--group-by "paths,tags" \
--keep-daily "$RESTIC_RETENTION_DAYS" \
--keep-weekly "$RESTIC_RETENTION_WEEKS" \
--keep-monthly "$RESTIC_RETENTION_MONTHS" \
--keep-yearly "$RESTIC_RETENTION_YEARS" &
wait $!
# Check repository for errors.
# NOTE this takes much time (and data transfer from remote repo?), do this in a separate systemd.timer which is run less often.
#restic check &
#wait $!
echo "Backup & cleaning is done."

42
bin/restic_check.sh Normal file
View File

@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# Check the backups made with restic to Backblaze B2 for errors.
# See restic_backup.sh on how this script is run (as it's analogous for this script).
# Exit on error, unset var, pipe failure
set -euo pipefail
# Clean up lock if we are killed.
# If killed by systemd, like $(systemctl stop restic), then it kills the whole cgroup and all it's subprocesses.
# However if we kill this script ourselves, we need this trap that kills all subprocesses manually.
exit_hook() {
echo "In exit_hook(), being killed" >&2
jobs -p | xargs kill
restic unlock
}
trap exit_hook INT TERM
# Assert that all needed environment variables are set.
assert_envvars() {
local varnames=("$@")
for varname in "${varnames[@]}"; do
if [ -z ${!varname+x} ]; then
printf "%s must be set for this script to work.\n\nDid you forget to source a /etc/restic/*.env profile in the current shell before executing this script?\n" "$varname" >&2
exit 1
fi
done
}
assert_envvars \
B2_ACCOUNT_ID B2_ACCOUNT_KEY B2_CONNECTIONS \
RESTIC_PASSWORD_FILE RESTIC_REPOSITORY RESTIC_VERBOSITY_LEVEL
# Remove locks from other stale processes to keep the automated backup running.
# NOTE nope, don't unlock like restic_backup.sh. restic_backup.sh should take precedence over this script.
#restic unlock &
#wait $!
# Check repository for errors.
restic check \
--option b2.connections="$B2_CONNECTIONS" \
--verbose="$RESTIC_VERBOSITY_LEVEL" &
wait $!

146
bin/resticw Executable file
View File

@@ -0,0 +1,146 @@
#!/usr/bin/env bash
DOC="A little wrapper over restic just to handle profiles and environment loading.
Usage:
resticw [options] <restic_arguments_line>...
The <restic_arguments_line> is just the regular unwrapped restic command arguments, e.g. stats latest
Options:
-p --profile=<name> Specify the profile to load or use default [default: default].
Examples:
resticw --profile profileA snapshots
resticw stats latest # this will use the profile: default
"
# The following argument parser is generated with docopt.sh from the above docstring.
# See https://github.com/andsens/docopt.sh. If the DOC is updated or new options are added, refresh the parser!
# docopt parser below, refresh this parser with `docopt.sh resticw`
# shellcheck disable=2016,1075,2154
docopt() { parse() { if ${DOCOPT_DOC_CHECK:-true}; then local doc_hash
if doc_hash=$(printf "%s" "$DOC" | (sha256sum 2>/dev/null || shasum -a 256)); then
if [[ ${doc_hash:0:5} != "$digest" ]]; then
stderr "The current usage doc (${doc_hash:0:5}) does not match \
what the parser was generated with (${digest})
Run \`docopt.sh\` to refresh the parser."; _return 70; fi; fi; fi
local root_idx=$1; shift; argv=("$@"); parsed_params=(); parsed_values=()
left=(); testdepth=0; local arg; while [[ ${#argv[@]} -gt 0 ]]; do
if [[ ${argv[0]} = "--" ]]; then for arg in "${argv[@]}"; do
parsed_params+=('a'); parsed_values+=("$arg"); done; break
elif [[ ${argv[0]} = --* ]]; then parse_long
elif [[ ${argv[0]} = -* && ${argv[0]} != "-" ]]; then parse_shorts
elif ${DOCOPT_OPTIONS_FIRST:-false}; then for arg in "${argv[@]}"; do
parsed_params+=('a'); parsed_values+=("$arg"); done; break; else
parsed_params+=('a'); parsed_values+=("${argv[0]}"); argv=("${argv[@]:1}"); fi
done; local idx; if ${DOCOPT_ADD_HELP:-true}; then
for idx in "${parsed_params[@]}"; do [[ $idx = 'a' ]] && continue
if [[ ${shorts[$idx]} = "-h" || ${longs[$idx]} = "--help" ]]; then
stdout "$trimmed_doc"; _return 0; fi; done; fi
if [[ ${DOCOPT_PROGRAM_VERSION:-false} != 'false' ]]; then
for idx in "${parsed_params[@]}"; do [[ $idx = 'a' ]] && continue
if [[ ${longs[$idx]} = "--version" ]]; then stdout "$DOCOPT_PROGRAM_VERSION"
_return 0; fi; done; fi; local i=0; while [[ $i -lt ${#parsed_params[@]} ]]; do
left+=("$i"); ((i++)) || true; done
if ! required "$root_idx" || [ ${#left[@]} -gt 0 ]; then error; fi; return 0; }
parse_shorts() { local token=${argv[0]}; local value; argv=("${argv[@]:1}")
[[ $token = -* && $token != --* ]] || _return 88; local remaining=${token#-}
while [[ -n $remaining ]]; do local short="-${remaining:0:1}"
remaining="${remaining:1}"; local i=0; local similar=(); local match=false
for o in "${shorts[@]}"; do if [[ $o = "$short" ]]; then similar+=("$short")
[[ $match = false ]] && match=$i; fi; ((i++)) || true; done
if [[ ${#similar[@]} -gt 1 ]]; then
error "${short} is specified ambiguously ${#similar[@]} times"
elif [[ ${#similar[@]} -lt 1 ]]; then match=${#shorts[@]}; value=true
shorts+=("$short"); longs+=(''); argcounts+=(0); else value=false
if [[ ${argcounts[$match]} -ne 0 ]]; then if [[ $remaining = '' ]]; then
if [[ ${#argv[@]} -eq 0 || ${argv[0]} = '--' ]]; then
error "${short} requires argument"; fi; value=${argv[0]}; argv=("${argv[@]:1}")
else value=$remaining; remaining=''; fi; fi; if [[ $value = false ]]; then
value=true; fi; fi; parsed_params+=("$match"); parsed_values+=("$value"); done
}; parse_long() { local token=${argv[0]}; local long=${token%%=*}
local value=${token#*=}; local argcount; argv=("${argv[@]:1}")
[[ $token = --* ]] || _return 88; if [[ $token = *=* ]]; then eq='='; else eq=''
value=false; fi; local i=0; local similar=(); local match=false
for o in "${longs[@]}"; do if [[ $o = "$long" ]]; then similar+=("$long")
[[ $match = false ]] && match=$i; fi; ((i++)) || true; done
if [[ $match = false ]]; then i=0; for o in "${longs[@]}"; do
if [[ $o = $long* ]]; then similar+=("$long"); [[ $match = false ]] && match=$i
fi; ((i++)) || true; done; fi; if [[ ${#similar[@]} -gt 1 ]]; then
error "${long} is not a unique prefix: ${similar[*]}?"
elif [[ ${#similar[@]} -lt 1 ]]; then
[[ $eq = '=' ]] && argcount=1 || argcount=0; match=${#shorts[@]}
[[ $argcount -eq 0 ]] && value=true; shorts+=(''); longs+=("$long")
argcounts+=("$argcount"); else if [[ ${argcounts[$match]} -eq 0 ]]; then
if [[ $value != false ]]; then
error "${longs[$match]} must not have an argument"; fi
elif [[ $value = false ]]; then
if [[ ${#argv[@]} -eq 0 || ${argv[0]} = '--' ]]; then
error "${long} requires argument"; fi; value=${argv[0]}; argv=("${argv[@]:1}")
fi; if [[ $value = false ]]; then value=true; fi; fi; parsed_params+=("$match")
parsed_values+=("$value"); }; required() { local initial_left=("${left[@]}")
local node_idx; ((testdepth++)) || true; for node_idx in "$@"; do
if ! "node_$node_idx"; then left=("${initial_left[@]}"); ((testdepth--)) || true
return 1; fi; done; if [[ $((--testdepth)) -eq 0 ]]; then
left=("${initial_left[@]}"); for node_idx in "$@"; do "node_$node_idx"; done; fi
return 0; }; optional() { local node_idx; for node_idx in "$@"; do
"node_$node_idx"; done; return 0; }; oneormore() { local i=0
local prev=${#left[@]}; while "node_$1"; do ((i++)) || true
[[ $prev -eq ${#left[@]} ]] && break; prev=${#left[@]}; done
if [[ $i -ge 1 ]]; then return 0; fi; return 1; }; value() { local i
for i in "${!left[@]}"; do local l=${left[$i]}
if [[ ${parsed_params[$l]} = "$2" ]]; then
left=("${left[@]:0:$i}" "${left[@]:((i+1))}")
[[ $testdepth -gt 0 ]] && return 0; local value
value=$(printf -- "%q" "${parsed_values[$l]}"); if [[ $3 = true ]]; then
eval "var_$1+=($value)"; else eval "var_$1=$value"; fi; return 0; fi; done
return 1; }; stdout() { printf -- "cat <<'EOM'\n%s\nEOM\n" "$1"; }; stderr() {
printf -- "cat <<'EOM' >&2\n%s\nEOM\n" "$1"; }; error() {
[[ -n $1 ]] && stderr "$1"; stderr "$usage"; _return 1; }; _return() {
printf -- "exit %d\n" "$1"; exit "$1"; }; set -e; trimmed_doc=${DOC:0:450}
usage=${DOC:79:53}; digest=dc31d; shorts=(-p); longs=(--profile); argcounts=(1)
node_0(){ value __profile 0; }; node_1(){ value _restic_arguments_line_ a true
}; node_2(){ optional 0; }; node_3(){ optional 2; }; node_4(){ oneormore 1; }
node_5(){ required 3 4; }; node_6(){ required 5; }; cat <<<' docopt_exit() {
[[ -n $1 ]] && printf "%s\n" "$1" >&2; printf "%s\n" "${DOC:79:53}" >&2; exit 1
}'; unset var___profile var__restic_arguments_line_; parse 6 "$@"
local prefix=${DOCOPT_PREFIX:-''}; unset "${prefix}__profile" \
"${prefix}_restic_arguments_line_"
eval "${prefix}"'__profile=${var___profile:-default}'
if declare -p var__restic_arguments_line_ >/dev/null 2>&1; then
eval "${prefix}"'_restic_arguments_line_=("${var__restic_arguments_line_[@]}")'
else eval "${prefix}"'_restic_arguments_line_=()'; fi; local docopt_i=1
[[ $BASH_VERSION =~ ^4.3 ]] && docopt_i=2; for ((;docopt_i>0;docopt_i--)); do
declare -p "${prefix}__profile" "${prefix}_restic_arguments_line_"; done; }
# docopt parser above, complete command for generating this parser is `docopt.sh resticw`
# Parse arguments
eval "$(docopt "$@")" # See https://github.com/andsens/docopt.sh for the magic :)
# --^^^-- END OF GENERATED COMMAND LINE PARSING STUFF --^^^--
#
# --vvv-- ACTUAL SCRIPT BELOW --vvv--
# Exit on error, unbound variable, pipe error
set -euo pipefail
ENV_DIR=/etc/restic
ERR_NO_SUCH_PROFILE=2
ERR_PROFILE_NO_READ_PERM=3
# shellcheck disable=SC2154
profile_file="${ENV_DIR}/${__profile}.env"
[[ ! -f "$profile_file" ]] && echo "Invalid profile: No such environment file ${profile_file}" && exit "$ERR_NO_SUCH_PROFILE"
if [[ ! -r "$profile_file" ]]; then
echo "Error: could not read the environment file ${profile_file}. Are you running this script as the correct user? Maybe try sudo with the right user."
exit "$ERR_PROFILE_NO_READ_PERM"
fi
echo -e "‣ Using profile: ${__profile} -- (${profile_file})\n"
# shellcheck disable=SC2154,SC1090
source "$profile_file" && restic "$_restic_arguments_line_"

61
bin/systemd-email Normal file
View File

@@ -0,0 +1,61 @@
#!/usr/bin/env bash
# Send email notification from systemd.
# Source: https://serverfault.com/questions/876233/how-to-send-an-email-if-a-systemd-service-is-restarted
# Source: https://wiki.archlinux.org/index.php/Systemd/Timers#MAILTO
# Usage: systemd-email <recipient-email> <failed-systemd-unit-name>
# According to
# http://www.flashissue.com/blog/gmail-sending-limits/
# Gmail blocks your account if you send more than 500 emails per day, which is one email every
# (24 * 60 * 60) / 500 = 172.8 second => choose a min wait time which is significantly longer than this to be on the safe time to not exceed 500 emails per day.
# However this source
# https://group-mail.com/sending-email/email-send-limits-and-options/
# says the limit when not using the Gmail webinterface but going directly to the SMTP server is 100-150 per day, which yelds maximum one email every
# (24 * 60 * 60) / 100 = 864 second
# One option that I used with my old Axis cameras it to use my gmx.com accunt for sending emails instead, as there are (no?) higher limits there.
MIN_WAIT_TIME_S=900
SCRIPT_NAME=$(basename "$0")
LAST_RUN_FILE="/tmp/${SCRIPT_NAME}_last_run.txt"
last_touch() {
stat -c %Y "$1"
}
waited_long_enough() {
retval=1
if [ -e "$LAST_RUN_FILE" ]; then
now=$(date +%s)
last=$(last_touch "$LAST_RUN_FILE")
wait_s=$((now - last))
if [ "$wait_s" -gt "$MIN_WAIT_TIME_S" ]; then
retval=0
fi
else
retval=0
fi
[ $retval -eq 0 ] && touch "$LAST_RUN_FILE"
return $retval
}
# Make sure that my Gmail account dont' get shut down because of sending too many emails!
if ! waited_long_enough; then
echo "Systemd email was not sent, as it's less than ${MIN_WAIT_TIME_S} seconds since the last one was sent."
exit 1
fi
recipient=$1
system_unit=$2
sendmail -t <<ERRMAIL
To: $recipient
From: systemd <root@$HOSTNAME>
Subject: [systemd-email] ${system_unit}
Content-Transfer-Encoding: 8bit
Content-Type: text/plain; charset=UTF-8
$(systemctl status --full "$system_unit")
ERRMAIL