Compare commits

..

3 commits

Author SHA1 Message Date
Nicolas Roman 73fbbf4d0a added api call 2019-03-15 15:31:50 +01:00
Nicolas Roman 269336fcf0 fixed shellcheck warnings 2019-02-18 17:37:17 +01:00
Nicolas Roman 1836f04938 added timeout on read function 2019-02-15 15:34:19 +01:00
13 changed files with 211 additions and 952 deletions

View file

@ -1,98 +0,0 @@
The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
and this project **does not adhere to [Semantic Versioning](http://semver.org/spec/v2.0.0.html)**.
## [Unreleased]
### Added
### Changed
### Deprecated
### Removed
### Fixed
### Security
## [23.10.1] - 2023-10-09
### Fixed
* Use a special variable name since USER is always defined from the environment
## [23.10] - 2023-10-09
### Added
* Force a user name with `-u,--user` option (default is still `logname(1)`).
* More people credited
### Deprecated
* `--autosysadmin` is replaced by `--user autosysadmin`
## [22.07] - 2022-07-05
### Added
* Add `--autosysadmin` flag
* Commit change in /etc of lxc containers
### Changed
### Deprecated
### Removed
### Fixed
### Security
## [22.01] - 2022-01-25
### Added
* version/host/user headers in sent email
### Changed
New version pattern
## [0.6.4] - 2021-06-17
### Added
* fallback if findmnt is absent
## [0.6.3] - 2020-02-02
### Added
* Notify syslog when partitions are re-mounted (Linux)
## [0.6.2] - 2020-02-02
### Fixed
* better detection of read-only partitions (Linux)
## [0.6.0] - 2019-11-05
### Added
* commit changes in /usr/share/scripts/ if needed
## Previous changelog
* 0.5.0 : options et mode interactif pour l'exécution des actions, meilleure compatibilité POSIX
* 0.4.1 : Utilisation de "printf" à la place de "echo" pour mieux gérer les sauts de ligne
* 0.4.0 : Amélioration de la récupération d'information (plus de cas gérés). Infos Git avant la saisie.
* 0.3.0 : Écriture dans un fichier de log, amélioration de la récupération d'informations, amélioration de la syntaxe shell
* 0.2.7 : Correction d'un bug lors de l'utilisation de '&' dans le texte
* 0.2.6 : Precision du charset dans les entetes du mail
* 0.2.5 : Correction d'un bug avec le path de sendmail sous OpenBSD
* 0.2.4 : Correction d'un bug lors de l'utilisation de '/' dans le texte
* 0.2.3 : Correction d'un bug avec $REALM

7
README Normal file
View file

@ -0,0 +1,7 @@
script for Evolix maintenance
=============================
script evomaintenance.sh which sends a mail
for each intervention on a Pack Evolix server.
* Pb quand '&' dans le msg d'evomaintenance...

View file

@ -1,30 +0,0 @@
# Evomaintenance
```.plain
$ evomaintenance --help
evomaintenance is a program that helps reporting what you've done on a server
Usage: evomaintenance
or evomaintenance --message="add new host"
or evomaintenance --no-api --no-mail --no-commit
or echo "add new vhost" | evomaintenance
Options
-m, --message=MESSAGE set the message from the command line
--mail enable the mail hook (default)
--no-mail disable the mail hook
--db enable the database hook
--no-db disable the database hook (default)
--api enable the API hook (default)
--no-api disable the API hook
--commit enable the commit hook (default)
--no-commit disable the commit hook
--evocheck enable evocheck execution (default)
--no-evocheck disable evocheck execution
--auto use "auto" mode
--no-auto use "manual" mode (default)
-v, --verbose increase verbosity
-n, --dry-run actions are not executed
--help print this message and exit
--version print version and exit
```

5
Vagrantfile vendored
View file

@ -10,7 +10,7 @@ load File.expand_path(vagrantfile) if File.exists?(vagrantfile)
Vagrant.configure('2') do |config| Vagrant.configure('2') do |config|
config.vm.synced_folder "./", "/vagrant", type: "rsync", rsync__exclude: [ '.vagrant', '.git' ] config.vm.synced_folder "./", "/vagrant", type: "rsync", rsync__exclude: [ '.vagrant', '.git' ]
config.ssh.shell = "/bin/sh" config.ssh.shell = "/bin/sh"
$deps = <<SCRIPT $deps = <<SCRIPT
DEBIAN_FRONTEND=noninteractive apt-get -yq install postgresql-client sudo sendmail DEBIAN_FRONTEND=noninteractive apt-get -yq install postgresql-client sudo sendmail
SCRIPT SCRIPT
@ -22,6 +22,7 @@ if [ ! -d /usr/share/scripts ]; then
chown root:$1 /usr/share/scripts chown root:$1 /usr/share/scripts
fi fi
ln -fs /vagrant/evomaintenance.sh /usr/share/scripts ln -fs /vagrant/evomaintenance.sh /usr/share/scripts
ln -fs /vagrant/evomaintenance.tpl /usr/share/scripts
ln -fs /vagrant/evomaintenance.cf /etc ln -fs /vagrant/evomaintenance.cf /etc
SCRIPT SCRIPT
@ -49,7 +50,7 @@ SCRIPT
config.vm.define "#{i[:name]}" do |node| config.vm.define "#{i[:name]}" do |node|
node.vm.hostname = "evomaintenance-#{i[:name]}" node.vm.hostname = "evomaintenance-#{i[:name]}"
node.vm.box = "#{i[:box]}" node.vm.box = "#{i[:box]}"
config.vm.provision "deps", type: "shell", :inline => $deps if "#{i[:name]}" == "debian" config.vm.provision "deps", type: "shell", :inline => $deps if "#{i[:name]}" == "debian"
config.vm.provision "deps", type: "shell", :inline => "pkg_add postgresql-client-10.5p1" if "#{i[:name]}" == "openbsd" config.vm.provision "deps", type: "shell", :inline => "pkg_add postgresql-client-10.5p1" if "#{i[:name]}" == "openbsd"
config.vm.provision "install", type: "shell", :inline => $install, :args => ["#{i[:group]}"] config.vm.provision "install", type: "shell", :inline => $install, :args => ["#{i[:group]}"]

8
changelog Normal file
View file

@ -0,0 +1,8 @@
0.4.1 : Utilisation de "printf" à la place de "echo" pour mieux gérer les sauts de ligne
0.4.0 : Amélioration de la récupération d'information (plus de cas gérés). Infos Git avant la saisie.
0.3.0 : Écriture dans un fichier de log, amélioration de la récupération d'informations, amélioration de la syntaxe shell
0.2.7 : Correction d'un bug lors de l'utilisation de '&' dans le texte
0.2.6 : Precision du charset dans les entetes du mail
0.2.5 : Correction d'un bug avec le path de sendmail sous OpenBSD
0.2.4 : Correction d'un bug lors de l'utilisation de '/' dans le texte
0.2.3 : Correction d'un bug avec $REALM

View file

@ -1,3 +0,0 @@
#!/bin/sh
# Git pre-checkout hook restoring permissions and ownerships.
mtree -u < .mtree

View file

@ -1,26 +0,0 @@
#!/bin/sh
# Git pre-commit hook storing permissions and ownerships.
mtreeignore=$(mktemp --suffix mtree)
mtree_exclude() {
echo .git
# Get ignored files from git https://stackoverflow.com/a/467053
find . -not -path './.git/*' | git check-ignore --stdin
}
# In case .mtree doens't exists yet, we still want it in the specification
# to be generated.
if [ -f .mtree ]; then
touch .mtree
fi
mtree_exclude > "$mtreeignore"
trap 'rm --force "$mtreeignore"' EXIT
mtree -x -c \
-p . \
-k uname,gname,mode \
-X "$mtreeignore" > .mtree
git add .mtree

38
debian/changelog vendored
View file

@ -1,32 +1,8 @@
evomaintenance (0.6.3-1) UNRELEASED; urgency=low evomaintenance (0.4.1-1) UNRELEASED; urgency=low
* fix partitions re-mounting before/after commits * Use "printf" instead of "echo" for better line breaks support
-- Jérémy Lecour <jlecour@evolix.fr> Mon, 3 Mar 2020 22:14:12 +0100 -- Jérémy Lecour <jlecour@evolix.fr> Tue, 25 Sep 2018 11:54:12 +0200
evomaintenance (0.6.0-1) UNRELEASED; urgency=low
* commit changes in /usr/share/scripts/ if needed
-- Jérémy Lecour <jlecour@evolix.fr> Tue, 5 Nov 2019 14:50:12 +0100
evomaintenance (0.5.1-1) UNRELEASED; urgency=low
* verify commands presence only if needed
-- Jérémy Lecour <jlecour@evolix.fr> Wed, 21 Aug 2019 15:38:12 +0200
evomaintenance (0.5.0-1) UNRELEASED; urgency=low
* options and interactive mode for selective hooks execution, and better POSIX compliance
-- Jérémy Lecour <jlecour@evolix.fr> Sun, 26 Mar 2019 11:13:12 +0100
evomaintenance (0.4.1-1) UNRELEASED; urgency=low
* Use "printf" instead of "echo" for better line breaks support
-- Jérémy Lecour <jlecour@evolix.fr> Tue, 25 Sep 2018 11:54:12 +0200
evomaintenance (0.4.0-1) UNRELEASED; urgency=low evomaintenance (0.4.0-1) UNRELEASED; urgency=low
@ -57,7 +33,7 @@ evomaintenance (0.2.8-1) UNRELEASED; urgency=medium
evomaintenance (0.2.7-1) UNRELEASED; urgency=low evomaintenance (0.2.7-1) UNRELEASED; urgency=low
* New upstream release. * New upstream release.
* This version fixes bug when text contains a character '&'. * This version fixes bug when text contains a character '&'.
-- Gregory Colpart <reg@debian.org> Thu, 11 Mar 2010 16:14:19 +0100 -- Gregory Colpart <reg@debian.org> Thu, 11 Mar 2010 16:14:19 +0100
@ -65,7 +41,7 @@ evomaintenance (0.2.6-1) UNRELEASED; urgency=low
* New upstream release. * New upstream release.
* This version fixes the bug for charset in mail. * This version fixes the bug for charset in mail.
* Add Build-Depends on debhelper. * Add Build-Depends on debhelper.
-- Gregory Colpart <reg@debian.org> Mon, 01 Jun 2009 15:57:32 +0200 -- Gregory Colpart <reg@debian.org> Mon, 01 Jun 2009 15:57:32 +0200
@ -86,13 +62,13 @@ evomaintenance (0.2.3-1) UNRELEASED; urgency=low
evomaintenance (0.2.2-1) UNRELEASED; urgency=low evomaintenance (0.2.2-1) UNRELEASED; urgency=low
* New upstream release. * New upstream release.
* Fix debian/rules to have correct permissions. * Fix debian/rules to have correct permissions.
-- Gregory Colpart <reg@debian.org> Mon, 10 Nov 2008 00:09:39 +0100 -- Gregory Colpart <reg@debian.org> Mon, 10 Nov 2008 00:09:39 +0100
evomaintenance (0.2.1-1) UNRELEASED; urgency=low evomaintenance (0.2.1-1) UNRELEASED; urgency=low
* New upstream release. * New upstream release.
-- Gregory Colpart <reg@debian.org> Sun, 9 Nov 2008 22:37:20 +0100 -- Gregory Colpart <reg@debian.org> Sun, 9 Nov 2008 22:37:20 +0100

3
debian/control vendored
View file

@ -7,7 +7,8 @@ Standards-Version: 3.8.0
Package: evomaintenance Package: evomaintenance
Architecture: all Architecture: all
Depends: coreutils, sudo, sed, hostname, postfix, git, postgresql-client Depends: postgresql-client, sudo
Description: script for Evolix maintenance Description: script for Evolix maintenance
This package contains the script evomaintenance.sh This package contains the script evomaintenance.sh
which sends a mail for each intervention on a Pack Evolix server. which sends a mail for each intervention on a Pack Evolix server.

7
debian/rules vendored
View file

@ -12,18 +12,19 @@ build: build-stamp
build-stamp: build-stamp:
dh_testdir dh_testdir
touch $@ touch $@
clean: clean:
dh_testdir dh_testdir
dh_testroot dh_testroot
rm -f build-stamp rm -f build-stamp
dh_clean dh_clean
install: build install: build
dh_testdir dh_testdir
dh_testroot dh_testroot
dh_clean -k dh_clean -k
dh_installdirs dh_installdirs
install -m 700 -d $(CURDIR)/debian/evomaintenance/usr/share/scripts install -m 700 -d $(CURDIR)/debian/evomaintenance/usr/share/scripts
install -m 700 evomaintenance.sh $(CURDIR)/debian/evomaintenance/usr/share/scripts/ install -m 700 evomaintenance.sh $(CURDIR)/debian/evomaintenance/usr/share/scripts/
install -m 600 evomaintenance.tpl $(CURDIR)/debian/evomaintenance/usr/share/scripts/
install -m 755 -d $(CURDIR)/debian/evomaintenance/etc install -m 755 -d $(CURDIR)/debian/evomaintenance/etc
install -m 600 evomaintenance.cf $(CURDIR)/debian/evomaintenance/etc/ install -m 600 evomaintenance.cf $(CURDIR)/debian/evomaintenance/etc/
# Build architecture-independent files here. # Build architecture-independent files here.

View file

@ -13,5 +13,3 @@ FULLFROM="John Doe <jdoe@example.com>"
URGENCYFROM=mama.doe@example.com URGENCYFROM=mama.doe@example.com
URGENCYTEL="06.00.00.00.00" URGENCYTEL="06.00.00.00.00"
REALM=example.com REALM=example.com
API_ENDPOINT=https://example.com/api/
API_KEY=secretkey

View file

@ -1,830 +1,221 @@
#!/bin/sh #!/bin/sh
VERSION="23.10.1" # EvoMaintenance script
# Dependencies (all OS): git postgresql-client
# Dependencies (Debian): sudo
show_version() { # version 0.4.1
cat <<END # Copyright 2007-2018 Gregory Colpart <reg@evolix.fr>, Jérémy Lecour <jlecour@evolix.fr>, Evolix <info@evolix.fr>
evomaintenance version ${VERSION}
Copyright 2007-2023 Evolix <info@evolix.fr>,
Gregory Colpart <reg@evolix.fr>,
Jérémy Lecour <jlecour@evolix.fr>,
Brice Waegeneire <bwaegeneire@evolix.fr>,
Mathieu Trossevin <mtrossevin@evolix.fr>
and others.
evomaintenance comes with ABSOLUTELY NO WARRANTY. This is free software,
and you are welcome to redistribute it under certain conditions.
See the GNU General Public Licence for details.
END
}
show_help() {
cat <<END
evomaintenance is a program that helps reporting what you've done on a server
Usage: evomaintenance
or evomaintenance --message="add new host"
or evomaintenance --no-api --no-mail --no-commit
or echo "add new vhost" | evomaintenance
Options
-m, --message=MESSAGE set the message from the command line
--mail enable the mail hook (default)
--no-mail disable the mail hook
--db enable the database hook
--no-db disable the database hook (default)
--api enable the API hook (default)
--no-api disable the API hook
--commit enable the commit hook (default)
--no-commit disable the commit hook
--evocheck enable evocheck execution (default)
--no-evocheck disable evocheck execution
--auto use "auto" mode
--no-auto use "manual" mode (default)
-u, --user=USER force USER value (default: logname(1))
-v, --verbose increase verbosity
-n, --dry-run actions are not executed
--help print this message and exit
-V, --version print version and exit
END
}
syslog() {
if [ -x "${LOGGER_BIN}" ]; then
${LOGGER_BIN} -t "evomaintenance" "$1"
fi
}
get_system() { get_system() {
uname -s uname -s
} }
get_fqdn() { get_fqdn() {
if [ "$(get_system)" = "Linux" ]; then if [ "$(get_system)" = "Linux" ]; then
hostname --fqdn hostname --fqdn
elif [ "$(get_system)" = "OpenBSD" ]; then elif [ "$(get_system)" = "OpenBSD" ]; then
hostname hostname
else else
echo "OS not detected!" echo "OS not detected!"
exit 1 exit 1
fi fi
} }
get_tty() { get_tty() {
if [ "$(get_system)" = "Linux" ]; then if [ "$(get_system)" = "Linux" ]; then
ps -o tty= | tail -1 ps -o tty= | tail -1
elif [ "$(get_system)" = "OpenBSD" ]; then elif [ "$(get_system)" = "OpenBSD" ]; then
env | grep SSH_TTY | cut -d"/" -f3 env | grep SSH_TTY | cut -d"/" -f3
else else
echo "OS not detected!" echo "OS not detected!"
exit 1 exit 1
fi fi
} }
get_who() { get_who() {
who=$(LC_ALL=C who -m | tr -s ' ') who=$(LC_ALL=C who -m)
if [ -n "${who}" ]; then if [ -n "${who}" ]; then
echo "${who}" echo "${who}"
else else
LC_ALL=C who | grep "$(get_tty)" | tr -s ' ' LC_ALL=C who | grep $(get_tty) | tr -s ' '
fi fi
} }
get_begin_date() { get_begin_date() {
# XXX A begin date isn't applicable when used in autosysadmin, so we echo "$(date "+%Y") $(echo $(get_who) | cut -d" " -f3,4,5)"
# use the same date as the end date.
if is_autosysadmin; then
get_end_date
else
printf "%s %s" "$(date "+%Y")" "$(get_who | cut -d" " -f3,4,5)"
fi
} }
get_ip() { get_ip() {
ip=$(get_who | cut -d" " -f6 | sed -e "s/^(// ; s/)$//") ip=$(echo $(get_who) | cut -d" " -f6 | sed -e "s/^(// ; s/)$//")
if is_autosysadmin || [ "${ip}" = ":0" ]; then [ -z "${ip}" ] && ip="unknown (no tty)"
ip="localhost" [ "${ip}" = ":0" ] && ip="localhost"
elif [ -z "${ip}" ]; then
ip="unknown (no tty)"
fi
echo "${ip}" echo "${ip}"
} }
get_end_date() { get_end_date() {
date +"%Y %b %d %H:%M" date +"%Y %b %d %H:%M"
} }
get_now() { get_now() {
date +"%Y-%m-%dT%H:%M:%S%z" date +"%Y-%m-%dT%H:%M:%S%z"
} }
get_user() { # timeout on read(), uses TMOUT env as timer
if [ -n "${FORCE_USER}" ]; then timedout_read() {
echo "${FORCE_USER}" if [ -z "${TMOUT+x}" ] || [ "$TMOUT" = 0 ]; then
else # maximum allowed by stty
logname export TMOUT=25
fi fi
user_input=$1
old_tty_settings=$(stty -g)
stty -icanon min 0 time ${TMOUT}0
read -r "$user_input"
stty "$old_tty_settings"
unset TMOUT
} }
get_complete_hostname() {
REAL_HOSTNAME=$(get_fqdn)
if [ "${HOSTNAME}" = "${REAL_HOSTNAME}" ]; then
echo "${HOSTNAME}"
else
echo "${HOSTNAME} (${REAL_HOSTNAME})"
fi
}
get_repository_status() {
dir=$1
# tell Git where to find the repository and the work tree (no need to `cd …` there)
export GIT_DIR="${dir}/.git" GIT_WORK_TREE="${dir}"
# If the repository and the work tree exist, try to commit changes
if [ -d "${GIT_DIR}" ] && [ -d "${GIT_WORK_TREE}" ]; then
CHANGED_LINES=$(${GIT_BIN} status --porcelain | wc -l | tr -d ' ')
if [ "${CHANGED_LINES}" != "0" ]; then
STATUS=$(${GIT_BIN} status --short | tail -n ${GIT_STATUS_MAX_LINES})
printf "%s\n%s\n" "${GIT_DIR} (last ${GIT_STATUS_MAX_LINES} lines)" "${STATUS}" | sed -e '/^$/d'
fi
fi
# unset environment variables to prevent accidental influence on other git commands
unset GIT_DIR GIT_WORK_TREE
}
get_evocheck() {
if [ -x "${EVOCHECK_BIN}" ]; then
printf "Evocheck status :"
EVOCHECK_OUT=$(${EVOCHECK_BIN})
EVOCHECK_RC=$?
if [ "${EVOCHECK_RC}" = "0" ] && [ -z "${EVOCHECK_OUT}" ]; then
printf " OK\n\n"
else
printf " ERROR\n%s\n\n" "${EVOCHECK_OUT}"
fi
fi
}
print_log() {
printf "*********** %s ***************\n" "$(get_now)"
print_session_data
printf "Hooks : commit=%s db=%s api=%s mail=%s\n"\
"${HOOK_COMMIT}" "${HOOK_DB}" "${HOOK_API}" "${HOOK_MAIL}"
if [ "${HOOK_MAIL}" = "1" ]; then
printf "Mailto : %s\n" "${EVOMAINTMAIL}"
fi
}
print_session_data() {
printf "Host : %s\n" "${HOSTNAME_TEXT}"
printf "User : %s\n" "${USER}"
printf "IP : %s\n" "${IP}"
printf "Begin : %s\n" "${BEGIN_DATE}"
printf "End : %s\n" "${END_DATE}"
printf "Message : %s\n" "${MESSAGE}"
}
is_autosysadmin() {
test "${USER}" = "autosysadmin"
}
is_repository_readonly() {
if [ "$(get_system)" = "OpenBSD" ]; then
partition=$(stat -f '%Sd' $1)
mount | grep ${partition} | grep -q "read-only"
elif command -v findmnt >/dev/null; then
mountpoint=$(stat -c '%m' $1)
findmnt ${mountpoint} --noheadings --output OPTIONS -O ro
else
grep /usr /proc/mounts | grep -E '\bro\b'
fi
}
remount_repository_readwrite() {
if [ "$(get_system)" = "OpenBSD" ]; then
partition=$(stat -f '%Sd' $1)
mount -u -w /dev/${partition} 2>/dev/null
else
mountpoint=$(stat -c '%m' $1)
mount -o remount,rw ${mountpoint}
syslog "Re-mount ${mountpoint} as read-write to commit in repository $1"
fi
}
remount_repository_readonly() {
if [ "$(get_system)" = "OpenBSD" ]; then
partition=$(stat -f '%Sd' $1)
mount -u -r /dev/${partition} 2>/dev/null
else
mountpoint=$(stat -c '%m' $1)
mount -o remount,ro ${mountpoint} 2>/dev/null
syslog "Re-mount ${mountpoint} as read-only after commit to repository $1"
fi
}
hook_commit() {
if [ -x "${GIT_BIN}" ]; then
# loop on possible directories managed by GIT
for dir in ${GIT_REPOSITORIES}; do
# tell Git where to find the repository and the work tree (no need to `cd …` there)
export GIT_DIR="${dir}/.git" GIT_WORK_TREE="${dir}"
# reset variable used to track if a mount point is readonly
READONLY_ORIG=0
# If the repository and the work tree exist, try to commit changes
if [ -d "${GIT_DIR}" ] && [ -d "${GIT_WORK_TREE}" ]; then
CHANGED_LINES=$(${GIT_BIN} status --porcelain | wc -l | tr -d ' ')
if [ "${CHANGED_LINES}" != "0" ]; then
if [ "${DRY_RUN}" = "1" ]; then
# STATS_SHORT=$(${GIT_BIN} diff --stat | tail -1)
STATS=$(${GIT_BIN} diff --stat | tail -n ${GIT_STATUS_MAX_LINES})
# GIT_COMMITS_SHORT=$(printf "%s\n%s : %s" "${GIT_COMMITS_SHORT}" "${GIT_DIR}" "${STATS_SHORT}" | sed -e '/^$/d')
GIT_COMMITS=$(printf "%s\n%s\n%s" "${GIT_COMMITS}" "${GIT_DIR}" "${STATS}" | sed -e '/^$/d')
else
# remount mount point read-write if currently readonly
is_repository_readonly ${dir} && { READONLY_ORIG=1; remount_repository_readwrite ${dir}; }
# commit changes
${GIT_BIN} add --all
${GIT_BIN} commit --message "${MESSAGE}" --author="${USER} <${USER}@evolix.net>" --quiet
# remount mount point read-only if it was before
test "$READONLY_ORIG" = "1" && remount_repository_readonly ${dir}
# Add the SHA to the log file if something has been committed
SHA=$(${GIT_BIN} rev-parse --short HEAD)
# STATS_SHORT=$(${GIT_BIN} show --stat | tail -1)
STATS=$(${GIT_BIN} show --stat --pretty=format:"" | tail -n ${GIT_STATUS_MAX_LINES})
# append commit data, without empty lines
# GIT_COMMITS_SHORT=$(printf "%s\n%s : %s %s" "${GIT_COMMITS_SHORT}" "${GIT_DIR}" "${SHA}" "${STATS_SHORT}" | sed -e '/^$/d')
GIT_COMMITS=$(printf "%s\n%s : %s\n%s" "${GIT_COMMITS}" "${GIT_DIR}" "${SHA}" "${STATS}" | sed -e '/^$/d')
fi
fi
fi
# unset environment variables to prevent accidental influence on other git commands
unset GIT_DIR GIT_WORK_TREE
done
if [ -n "${GIT_COMMITS}" ]; then
# if [ "${VERBOSE}" = "1" ]; then
printf "\n********** Commits ****************\n%s\n***********************************\n" "${GIT_COMMITS}"
# fi
if [ "${DRY_RUN}" != "1" ]; then
echo "${GIT_COMMITS}" >> "${LOGFILE}"
fi
fi
fi
}
hook_db() {
SQL_DETAILS=$(echo "${MESSAGE}" | sed "s/'/''/g")
PG_QUERY="INSERT INTO evomaint(hostname,userid,ipaddress,begin_date,end_date,details) VALUES ('${HOSTNAME}','${USER}','${IP}','${BEGIN_DATE}',now(),'${SQL_DETAILS}')"
if [ "${VERBOSE}" = "1" ]; then
printf "\n********** DB query **************\n%s\n***********************************\n" "${PG_QUERY}"
fi
if [ "${DRY_RUN}" != "1" ] && [ -x "${PSQL_BIN}" ]; then
echo "${PG_QUERY}" | ${PSQL_BIN} "${PGDB}" "${PGTABLE}" -h "${PGHOST}"
fi
}
hook_api() {
if [ "${VERBOSE}" = "1" ]; then
printf "\n********** API call **************\n"
printf "curl -f -s -S -X POST [REDACTED] -k -F api_key=[REDACTED] -F action=insertEvoMaintenance -F hostname=%s -F userid=%s -F ipaddress=%s -F begin_date=%s -F end_date='now()' -F details=%s" \
"${HOSTNAME}" "${USER}" "${IP}" "${BEGIN_DATE}" "${MESSAGE}"
printf "\n***********************************\n"
fi
if [ "${DRY_RUN}" != "1" ] && [ -x "${CURL_BIN}" ]; then
API_RETURN_STATUS=$(curl -f -s -S -X POST \
"${API_ENDPOINT}" -k \
-F api_key="${API_KEY}" \
-F action=insertEvoMaintenance \
-F hostname="${HOSTNAME}" \
-F userid="${USER}" \
-F ipaddress="${IP}" \
-F begin_date="${BEGIN_DATE}" \
-F end_date='now()' \
-F details="${MESSAGE}")
# either cURL or the API backend can throw an error, otherwise it returns this JSON response
if [ "$API_RETURN_STATUS" = '{"status":"Ok"}' ]; then
echo "API call OK."
else
echo "API call FAILED."
fi
fi
}
format_mail() {
cat <<EOTEMPLATE
From: ${FULLFROM}
Content-Type: text/plain; charset=UTF-8
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
X-Evomaintenance-Version: ${VERSION}
X-Evomaintenance-Host: ${HOSTNAME_TEXT}
X-Evomaintenance-User: ${USER}
To: ${EVOMAINTMAIL}
Subject: [evomaintenance] Intervention sur ${HOSTNAME_TEXT} (${USER})
Bonjour,
Une intervention vient de se terminer sur votre serveur.
Nom du serveur : ${HOSTNAME_TEXT}
Personne ayant réalisée l'intervention : ${USER}
Intervention réalisée depuis : ${IP}
Début de l'intervention : ${BEGIN_DATE}
Fin de l'intervention : ${END_DATE}
### Renseignements sur l'intervention
${MESSAGE}
###
EOTEMPLATE
if [ -n "${GIT_COMMITS}" ]; then
cat << EOTEMPLATE
### Commits
${GIT_COMMITS}
###
EOTEMPLATE
fi
cat <<EOTEMPLATE
Pour réagir à cette intervention, vous pouvez répondre à ce message
(sur l'adresse mail ${FROM}). En cas d'urgence, utilisez
l'adresse ${URGENCYFROM} ou notre téléphone portable d'astreinte
(${URGENCYTEL})
Cordialement,
--
${FULLFROM}
EOTEMPLATE
}
hook_mail() {
MAIL_CONTENT=$(format_mail)
if [ "${VERBOSE}" = "1" ]; then
printf "\n********** Mail *******************\n%s\n***********************************\n" "${MAIL_CONTENT}"
fi
if [ "${DRY_RUN}" != "1" ] && [ -x "${SENDMAIL_BIN}" ]; then
echo "${MAIL_CONTENT}" | ${SENDMAIL_BIN} -oi -t -f "${FROM}"
fi
}
hook_log() {
if [ "${VERBOSE}" = "1" ]; then
print_log
fi
if [ "${DRY_RUN}" != "1" ]; then
print_log >> "${LOGFILE}"
fi
}
# load configuration if present.
test -f /etc/evomaintenance.cf && . /etc/evomaintenance.cf test -f /etc/evomaintenance.cf && . /etc/evomaintenance.cf
HOSTNAME=${HOSTNAME:-$(get_fqdn)} [ -n "${HOSTNAME}" ] || HOSTNAME=$(get_fqdn)
EVOMAINTMAIL=${EVOMAINTMAIL:-"evomaintenance-$(echo "${HOSTNAME}" | cut -d- -f1)@${REALM}"} [ -n "${EVOMAINTMAIL}" ] || EVOMAINTMAIL=evomaintenance-$(echo "${HOSTNAME}" | cut -d- -f1)@${REALM}
LOGFILE=${LOGFILE:-"/var/log/evomaintenance.log"} [ -n "${LOGFILE}" ] || LOGFILE=/var/log/evomaintenance.log
HOOK_COMMIT=${HOOK_COMMIT:-"1"}
HOOK_DB=${HOOK_DB:-"0"}
HOOK_API=${HOOK_API:-"1"}
HOOK_MAIL=${HOOK_MAIL:-"1"}
DRY_RUN=${DRY_RUN:-"0"}
VERBOSE=${VERBOSE:-"0"}
AUTO=${AUTO:-"0"}
EVOCHECK=${EVOCHECK:-"0"}
GIT_STATUS_MAX_LINES=${GIT_STATUS_MAX_LINES:-20}
API_ENDPOINT=${API_ENDPOINT:-""}
FORCE_USER=${FORCE_USER:-""}
# initialize variables
MESSAGE=""
# GIT_COMMITS_SHORT=""
GIT_COMMITS=""
# Parse options
# based on https://gist.github.com/deshion/10d3cb5f88a21671e17a
while :; do
case $1 in
-h|-\?|--help)
show_help
exit 0
;;
-V|--version)
show_version
exit 0
;;
-m|--message)
# message options, with value speparated by space
if [ -n "$2" ]; then
MESSAGE=$2
shift
else
printf 'ERROR: "--message" requires a non-empty option argument.\n' >&2
exit 1
fi
;;
--message=?*)
# message options, with value speparated by =
MESSAGE=${1#*=}
;;
--message=)
# message options, without value
printf 'ERROR: "--message" requires a non-empty option argument.\n' >&2
exit 1
;;
--no-commit)
# disable commit hook
HOOK_COMMIT=0
;;
--commit)
# enable commit hook
HOOK_COMMIT=1
;;
--no-db)
# disable DB hook
HOOK_DB=0
;;
--db)
# enable DB hook
HOOK_DB=1
;;
--no-api)
# disable API hook
HOOK_API=0
;;
--api)
# enable API hook
HOOK_API=1
;;
--no-mail)
# disable mail hook
HOOK_MAIL=0
;;
--mail)
# enable mail hook
HOOK_MAIL=1
;;
--no-auto)
# use "manual" mode
AUTO=0
;;
--auto)
# use "auto" mode
AUTO=1
;;
--autosysadmin)
# Deprecated, backward compatibility
# author change as autosysadmin
printf 'WARNING: "--autosysadmin" is deprecated, use "--user autosysadmin".\n' >&2
FORCE_USER="autosysadmin"
;;
-u|--user)
# user options, with value speparated by space
if [ -n "$2" ]; then
FORCE_USER=$2
shift
else
printf 'ERROR: "--user" requires a non-empty option argument.\n' >&2
exit 1
fi
;;
--user=?*)
# message options, with value speparated by =
FORCE_USER=${1#*=}
;;
--user=)
# message options, without value
printf 'ERROR: "--user" requires a non-empty option argument.\n' >&2
exit 1
;;
-n|--dry-run)
# disable actual commands
DRY_RUN=1
;;
-v|--verbose)
# print verbose information
VERBOSE=1
;;
--)
# End of all options.
shift
break
;;
-?*|[[:alnum:]]*)
# ignore unknown options
printf 'WARN: Unknown option (ignored): %s\n' "$1" >&2
;;
*)
# Default case: If no more options then break out of the loop.
break
;;
esac
shift
done
# Treat unset variables as an error when substituting. # Treat unset variables as an error when substituting.
# Only after this line, because some config variables might be missing. # Only after this line, because some config variables might be missing.
set -u set -u
# Gather information REAL_HOSTNAME=$(get_fqdn)
HOSTNAME_TEXT=$(get_complete_hostname) if [ "${HOSTNAME}" = "${REAL_HOSTNAME}" ]; then
HOSTNAME_TEXT="${HOSTNAME}"
else
HOSTNAME_TEXT="${HOSTNAME} (${REAL_HOSTNAME})"
fi
# TTY=$(get_tty) # TTY=$(get_tty)
# WHO=$(get_who) # WHO=$(get_who)
IP=$(get_ip) IP=$(get_ip)
BEGIN_DATE=$(get_begin_date) BEGIN_DATE=$(get_begin_date)
END_DATE=$(get_end_date) END_DATE=$(get_end_date)
USER=$(get_user) USER=$(logname)
PATH=${PATH}:/usr/sbin PATH=${PATH}:/usr/sbin
SENDMAIL_BIN=$(command -v sendmail) SENDMAIL_BIN=$(command -v sendmail)
readonly SENDMAIL_BIN
if [ "${HOOK_MAIL}" = "1" ] && [ -z "${SENDMAIL_BIN}" ]; then
echo "No \`sendmail' command has been found, can't send mail." 2>&1
fi
GIT_BIN=$(command -v git) GIT_BIN=$(command -v git)
readonly GIT_BIN
if [ "${HOOK_COMMIT}" = "1" ] && [ -z "${GIT_BIN}" ]; then
echo "No \`git' command has been found, can't commit changes" 2>&1
fi
PSQL_BIN=$(command -v psql) GIT_REPOSITORIES="/etc /etc/bind"
readonly PSQL_BIN
if [ "${HOOK_DB}" = "1" ] && [ -z "${PSQL_BIN}" ]; then
echo "No \`psql' command has been found, can't save to the database." 2>&1
fi
CURL_BIN=$(command -v curl)
readonly CURL_BIN
if [ "${HOOK_API}" = "1" ] && [ -z "${CURL_BIN}" ]; then
echo "No \`curl' command has been found, can't call the API." 2>&1
fi
LOGGER_BIN=$(command -v logger)
readonly LOGGER_BIN
if [ "${HOOK_API}" = "1" ] && [ -z "${API_ENDPOINT}" ]; then
echo "No API endpoint specified, can't call the API." 2>&1
fi
EVOCHECK_BIN="/usr/share/scripts/evocheck.sh"
GIT_REPOSITORIES="/etc /etc/bind /usr/share/scripts"
# Add /etc directories from lxc containers if they are git directories
if [ -d /var/lib/lxc ]; then
GIT_REPOSITORIES="${GIT_REPOSITORIES} $(find -L /var/lib/lxc/ -maxdepth 3 -name 'etc' | tr '\n' ' ' | sed 's/[[:space:]]\+$//')"
fi
# initialize variable
GIT_STATUSES=""
# git statuses # git statuses
if [ -x "${GIT_BIN}" ]; then GIT_STATUSES=""
if test -x "${GIT_BIN}"; then
# loop on possible directories managed by GIT # loop on possible directories managed by GIT
for dir in ${GIT_REPOSITORIES}; do for dir in ${GIT_REPOSITORIES}; do
RESULT=$(get_repository_status "${dir}") # tell Git where to find the repository and the work tree (no need to `cd …` there)
if [ -n "${RESULT}" ]; then export GIT_DIR="${dir}/.git" GIT_WORK_TREE="${dir}"
# append diff data, without empty lines # If the repository and the work tree exist, try to commit changes
GIT_STATUSES=$(printf "%s\n%s\n" "${GIT_STATUSES}" "${RESULT}" | sed -e '/^$/d') if test -d "${GIT_DIR}" && test -d "${GIT_WORK_TREE}"; then
CHANGED_LINES=$(${GIT_BIN} status --porcelain | wc -l | tr -d ' ')
if [ "${CHANGED_LINES}" != "0" ]; then
STATUS=$(${GIT_BIN} status --short | tail -n 10)
# append diff data, without empty lines
GIT_STATUSES=$(printf "%s\n%s\n%s\n" "${GIT_STATUSES}" "${GIT_DIR} (last 10 lines)" "${STATUS}" | sed -e '/^$/d')
fi
fi fi
unset RESULT # unset environment variables to prevent accidental influence on other git commands
unset GIT_DIR GIT_WORK_TREE
done done
fi if [ -n "${GIT_STATUSES}" ]; then
echo "/!\ There are some uncommited changes. If you proceed, everything will be commited."
# find out if running in interactive mode, or not echo "${GIT_STATUSES}"
if [ -t 0 ]; then echo ""
INTERACTIVE=1
else
INTERACTIVE=0
fi
readonly INTERACTIVE
if [ "${INTERACTIVE}" = "1" ] && [ "${EVOCHECK}" = "1" ]; then
get_evocheck
fi
if [ -n "${GIT_STATUSES}" ] && [ "${INTERACTIVE}" = "1" ]; then
printf "/!\\\ There are some uncommited changes.\n%s\n\n" "${GIT_STATUSES}"
fi
if [ -z "${MESSAGE}" ]; then
if [ "${INTERACTIVE}" = "1" ]; then
printf "> Please, enter details about your maintenance:\n"
fi fi
read -r MESSAGE
fi fi
if [ -z "${MESSAGE}" ]; then # get input from stdin
echo "> Please, enter details about your maintenance"
timedout_read TEXTE
if [ "${TEXTE}" = "" ]; then
echo "no value..." echo "no value..."
exit 1 exit 1
fi fi
print_session_data # recapitulatif
BLOB=$(cat <<END
Host : $HOSTNAME_TEXT
User : $USER
IP : $IP
Begin : $BEGIN_DATE
End : $END_DATE
Message : $TEXTE
END
)
if [ "${INTERACTIVE}" = "1" ] && [ "${AUTO}" = "0" ]; then echo ""
if [ "${HOOK_COMMIT}" = "1" ] || [ "${HOOK_MAIL}" = "1" ] || [ "${HOOK_DB}" = "1" ]; then echo "${BLOB}"
printf "\nActions to execute:\n" echo ""
if [ "${HOOK_COMMIT}" = "1" ]; then echo "> Press <Enter> to submit, or <Ctrl+c> to cancel."
printf "* commit changes in repositories\n" timedout_read enter
fi
if [ "${HOOK_MAIL}" = "1" ]; then
printf "* send mail to %s\n" "${EVOMAINTMAIL}"
fi
if [ "${HOOK_DB}" = "1" ]; then
printf "* save metadata to the database\n"
fi
if [ "${HOOK_API}" = "1" ]; then
printf "* send metadata to the API\n"
fi
echo ""
answer="" # write log
while :; do echo "----------- $(get_now) ---------------" >> "${LOGFILE}"
printf "> Let's continue? [Y,n,i,?] " echo "${BLOB}" >> "${LOGFILE}"
read -r answer
case $answer in # git commit
[Yy]|"" ) GIT_COMMITS=""
# force "auto" mode, but keep hooks settings
AUTO=1 if test -x "${GIT_BIN}"; then
break # loop on possible directories managed by GIT
;; for dir in ${GIT_REPOSITORIES}; do
[Nn] ) # tell Git where to find the repository and the work tree (no need to `cd …` there)
# force "auto" mode, and disable all hooks export GIT_DIR="${dir}/.git" GIT_WORK_TREE="${dir}"
HOOK_COMMIT=0 # If the repository and the work tree exist, try to commit changes
HOOK_MAIL=0 if test -d "${GIT_DIR}" && test -d "${GIT_WORK_TREE}"; then
HOOK_DB=0 CHANGED_LINES=$(${GIT_BIN} status --porcelain | wc -l | tr -d ' ')
HOOK_API=0 if [ "${CHANGED_LINES}" != "0" ]; then
AUTO=1 ${GIT_BIN} add --all
break ${GIT_BIN} commit --message "${TEXTE}" --author="${USER} <${USER}@evolix.net>" --quiet
;; # Add the SHA to the log file if something has been committed
[Ii] ) SHA=$(${GIT_BIN} rev-parse --short HEAD)
# force "manual" mode STATS=$(${GIT_BIN} show --stat | tail -1)
AUTO=0 # append commit data, without empty lines
break GIT_COMMITS=$(printf "%s\n%s : %s %s" "${GIT_COMMITS}" "${GIT_DIR}" "${SHA}" "${STATS}" | sed -e '/^$/d')
;; fi
* ) fi
printf "y - yes, execute actions and exit\n" # unset environment variables to prevent accidental influence on other git commands
printf "n - no, don't execute actions and exit\n" unset GIT_DIR GIT_WORK_TREE
printf "i - switch to interactive mode\n" done
printf "? - print this help\n" if [ -n "${GIT_COMMITS}" ]; then
;; echo "${GIT_COMMITS}" >> "${LOGFILE}"
esac
done
fi fi
fi fi
if [ "${INTERACTIVE}" = "1" ] && [ "${AUTO}" = "0" ]; then # insert into PG
# Commit hook # SQL_TEXTE=`echo "${TEXTE}" | sed "s/'/\\\\\\'/g ; s@/@\\\\\/@g ; s@\\&@et@g"`
if [ -n "${GIT_STATUSES}" ] && [ "${HOOK_COMMIT}" = "1" ]; then SQL_TEXTE=`echo "${TEXTE}" | sed "s/'/''/g"`
printf "/!\ There are some uncommited changes.\n%s\n\n" "${GIT_STATUSES}"
y="Y"; n="n" PG_QUERY="INSERT INTO evomaint(hostname,userid,ipaddress,begin_date,end_date,details) VALUES ('${HOSTNAME}','${USER}','${IP}','${BEGIN_DATE}',now(),'${SQL_TEXTE}')"
answer="" #echo "${PG_QUERY}" | psql ${PGDB} ${PGTABLE} -h ${PGHOST}
while :; do
printf "> Do you want to commit the changes? [%s] " "${y},${n}"
read -r answer
case $answer in
[Yy] )
hook_commit;
break
;;
[Nn] )
break
;;
"" )
if [ "${HOOK_COMMIT}" = "1" ]; then
hook_commit
fi
break
;;
* )
echo "answer with a valid choice"
;;
esac
done
fi
# Mail hook curl -H "Content-Type: application/json" -X POST http://172.28.128.3:8000 -d '{
if [ "${HOOK_MAIL}" = "1" ]; then "hostname": '${HOSTNAME}',
y="Y"; n="n" "userid": '${USER}',
else "ipaddress": '${IP}',
y="y"; n="N" "begin_date": '${BEGIN_DATE}',
fi "end_date": '$(now())',
answer="" "details": '${SQL_TEXTE}'
while :; do }'
printf "> Do you want to send an email to <%s>? [%s] " "${EVOMAINTMAIL}" "${y},${n},e"
read -r answer
case $answer in
[Yy] )
hook_mail;
break
;;
[Nn] )
break
;;
[Ee] )
printf "> To: [%s] " "${EVOMAINTMAIL}"
read -r mail_recipient
if [ -n "${mail_recipient}" ]; then
EVOMAINTMAIL="${mail_recipient}"
fi
;;
"" )
if [ "${HOOK_MAIL}" = "1" ]; then
hook_mail
fi
break
;;
* )
echo "answer with a valid choice"
;;
esac
done
# Database hook # send mail
if [ "${HOOK_DB}" = "1" ]; then MAIL_TEXTE=$(echo "${TEXTE}" | sed -e "s@/@\\\\\/@g ; s@&@\\\\&@")
y="Y"; n="n" MAIL_GIT_COMMITS=$(echo "${GIT_COMMITS}" | sed -e "s@/@\\\\\/@g ; s@&@\\\\&@")
else
y="y"; n="N"
fi
answer=""
while :; do
printf "> Do you want to insert your message into the database? [%s] " "${y},${n}"
read -r answer
case $answer in
[Yy] )
hook_db;
break
;;
[Nn] )
break
;;
"" )
if [ "${HOOK_DB}" = "1" ]; then
hook_db
fi
break
;;
* )
echo "answer with a valid choice"
;;
esac
done
# API hook cat /usr/share/scripts/evomaintenance.tpl | \
if [ "${HOOK_API}" = "1" ]; then sed -e "s/__TO__/${EVOMAINTMAIL}/ ; s/__HOSTNAME__/${HOSTNAME_TEXT}/ ; s/__USER__/${USER}/ ; s/__BEGIN_DATE__/${BEGIN_DATE}/ ; s/__END_DATE__/${END_DATE}/ ; s/__GIT_COMMITS__/${MAIL_GIT_COMMITS}/ ; s/__TEXTE__/${MAIL_TEXTE}/ ; s/__IP__/${IP}/ ; s/__FULLFROM__/${FULLFROM}/ ; s/__FROM__/${FROM}/ ; s/__URGENCYFROM__/${URGENCYFROM}/ ; s/__URGENCYTEL__/${URGENCYTEL}/" | \
y="Y"; n="n" ${SENDMAIL_BIN} -oi -t -f ${FROM}
else
y="y"; n="N"
fi
answer=""
while :; do
printf "> Do you want to send the metadata to the API? [%s] " "${y},${n}"
read -r answer
case $answer in
[Yy] )
hook_api;
break
;;
[Nn] )
break
;;
"" )
if [ "${HOOK_API}" = "1" ]; then
hook_api
fi
break
;;
* )
echo "answer with a valid choice"
;;
esac
done
fi
# Log hook
hook_log
if [ "${INTERACTIVE}" = "0" ] || [ "${AUTO}" = "1" ]; then
if [ "${HOOK_COMMIT}" = "1" ]; then
hook_commit
fi
if [ "${HOOK_MAIL}" = "1" ]; then
hook_mail
fi
if [ "${HOOK_DB}" = "1" ]; then
hook_db
fi
if [ "${HOOK_API}" = "1" ]; then
hook_api
fi
fi
exit 0 exit 0

33
evomaintenance.tpl Normal file
View file

@ -0,0 +1,33 @@
From: __FULLFROM__
Content-Type: text/plain; charset=UTF-8
MIME-Version: 1.0
Content-Transfer-Encoding: 8bit
To: __TO__
Subject: [evomaintenance] Intervention sur __HOSTNAME__ (__USER__)
Bonjour,
Une intervention vient de se terminer sur votre serveur.
Voici les renseignements sur l'intervention :
Nom du serveur : __HOSTNAME__
Personne ayant réalisée l'intervention : __USER__
Intervention réalisée depuis : __IP__
Début de l'intervention : __BEGIN_DATE__
Fin de l'intervention : __END_DATE__
###
Renseignements sur l'intervention :
__TEXTE__
###
__GIT_COMMITS__
Pour réagir à cette intervention, vous pouvez répondre à ce message
(sur l'adresse mail __FROM__). En cas d'urgence, utilisez
l'adresse __URGENCYFROM__ ou notre téléphone portable d'astreinte
(__URGENCYTEL__)
Cordialement,
--
__FULLFROM__