etc-git: optimize maintenance tasks
* manage commits with an optimized shell script instead of many slow Ansible tasks * centralize cron jobs in dedicated crontab
This commit is contained in:
parent
37cb18f676
commit
7b14296503
|
@ -28,7 +28,8 @@ The **patch** part changes is incremented if multiple releases happen the same m
|
||||||
* apache: new variable for mpm mode (+ updated default config accordingly)
|
* apache: new variable for mpm mode (+ updated default config accordingly)
|
||||||
* certbot: add script for manual deploy hooks execution
|
* certbot: add script for manual deploy hooks execution
|
||||||
* docker-host: install additional dependencies
|
* docker-host: install additional dependencies
|
||||||
* etc-git: purge old .git/index.lock (default: True)
|
* etc-git: manage commits with an optimized shell script instead of many slow Ansible tasks
|
||||||
|
* etc-git: centralize cron jobs in dedicated crontab
|
||||||
* evolinux-base: install molly-guard by default
|
* evolinux-base: install molly-guard by default
|
||||||
* generate-ldif: detect hardware raid card
|
* generate-ldif: detect hardware raid card
|
||||||
* generate-ldif: detect mdadm
|
* generate-ldif: detect mdadm
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
repositories="/etc /etc/bind/ /usr/share/scripts"
|
||||||
|
|
||||||
|
for repository in ${repositories}; do
|
||||||
|
if [ -d "${repository}/.git" ]; then
|
||||||
|
git --git-dir="${repository}/.git" gc --quiet
|
||||||
|
fi
|
||||||
|
done
|
|
@ -0,0 +1,11 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
repositories="/etc /etc/bind/ /usr/share/scripts"
|
||||||
|
|
||||||
|
for repository in ${repositories}; do
|
||||||
|
if [ -d "${repository}/.git" ]; then
|
||||||
|
git --git-dir="${repository}/.git" --work-tree="${repository}" status --short
|
||||||
|
fi
|
||||||
|
done
|
|
@ -0,0 +1,227 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
set -u
|
||||||
|
|
||||||
|
VERSION="21.10"
|
||||||
|
|
||||||
|
show_version() {
|
||||||
|
cat <<END
|
||||||
|
evocommit version ${VERSION}
|
||||||
|
|
||||||
|
Copyright 2021 Evolix <info@evolix.fr>,
|
||||||
|
Jérémy Lecour <jlecour@evolix.fr>
|
||||||
|
and others.
|
||||||
|
|
||||||
|
evocommit 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
|
||||||
|
evocommit helps properly committing changes in a repository
|
||||||
|
|
||||||
|
END
|
||||||
|
show_usage
|
||||||
|
}
|
||||||
|
show_usage() {
|
||||||
|
cat <<END
|
||||||
|
Usage: evocommit --repository /path/to/repository --message "add new host"
|
||||||
|
|
||||||
|
Options
|
||||||
|
--repository PATH set the path for the repository
|
||||||
|
--message MESSAGE set the commit message
|
||||||
|
-V, --version print version number
|
||||||
|
-v, --verbose increase verbosity
|
||||||
|
-n, --dry-run actions are not executed
|
||||||
|
--help print this message and exit
|
||||||
|
--version print version and exit
|
||||||
|
END
|
||||||
|
}
|
||||||
|
|
||||||
|
syslog() {
|
||||||
|
if [ -x "${LOGGER_BIN}" ]; then
|
||||||
|
${LOGGER_BIN} -t "evocommit" "$1"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
get_system() {
|
||||||
|
uname -s
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
is_dry_run() {
|
||||||
|
test "${DRY_RUN}" = "1"
|
||||||
|
}
|
||||||
|
main() {
|
||||||
|
|
||||||
|
lock="${GIT_DIR}/index.lock"
|
||||||
|
if [ -f "${lock}" ]; then
|
||||||
|
limit=$(date +"%s" -d "now - 1 hour")
|
||||||
|
updated_at=$(stat -c "%Y" "${lock}")
|
||||||
|
if [ "$updated_at" -lt "$limit" ]; then
|
||||||
|
rm -f "${lock}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
git_status=$(${GIT_BIN} status --porcelain)
|
||||||
|
|
||||||
|
if [ -n "${git_status}" ]; then
|
||||||
|
if is_dry_run; then
|
||||||
|
${GIT_BIN} status
|
||||||
|
else
|
||||||
|
readonly_orig=0
|
||||||
|
# remount mount point read-write if currently readonly
|
||||||
|
if is_repository_readonly "${REPOSITORY}"; then
|
||||||
|
readonly_orig=1;
|
||||||
|
remount_repository_readwrite "${REPOSITORY}";
|
||||||
|
fi
|
||||||
|
author=$(logname)
|
||||||
|
email=$(git config --get user.email)
|
||||||
|
email=${email:-"${author}@evolix.net"}
|
||||||
|
# commit changes
|
||||||
|
${GIT_BIN} add --all
|
||||||
|
${GIT_BIN} commit --message "${MESSAGE}" --author "${author} <${email}>" --quiet
|
||||||
|
# remount mount point read-only if it was before
|
||||||
|
if [ ${readonly_orig} -eq 1 ]; then
|
||||||
|
remount_repository_readonly "${REPOSITORY}"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
unset GIT_DIR
|
||||||
|
unset GIT_WORK_TREE
|
||||||
|
}
|
||||||
|
# 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
|
||||||
|
;;
|
||||||
|
--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
|
||||||
|
;;
|
||||||
|
--repository)
|
||||||
|
# repository options, with value speparated by space
|
||||||
|
if [ -n "$2" ]; then
|
||||||
|
REPOSITORY=$2
|
||||||
|
shift
|
||||||
|
else
|
||||||
|
printf 'ERROR: "--repository" requires a non-empty option argument.\n' >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
--repository=?*)
|
||||||
|
# repository options, with value speparated by =
|
||||||
|
REPOSITORY=${1#*=}
|
||||||
|
;;
|
||||||
|
--repository=)
|
||||||
|
# repository options, without value
|
||||||
|
printf 'ERROR: "--repository" 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
|
||||||
|
|
||||||
|
if [ -z "${MESSAGE}" ]; then
|
||||||
|
echo "Error: missing message parameter" >&2
|
||||||
|
show_usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
if [ -z "${REPOSITORY}" ]; then
|
||||||
|
echo "Error: missing repository parameter" >&2
|
||||||
|
show_usage
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
DRY_RUN=${DRY_RUN:-0}
|
||||||
|
VERBOSE=${VERBOSE:-0}
|
||||||
|
|
||||||
|
GIT_BIN=$(command -v git)
|
||||||
|
readonly GIT_BIN
|
||||||
|
|
||||||
|
LOGGER_BIN=$(command -v logger)
|
||||||
|
readonly LOGGER_BIN
|
||||||
|
|
||||||
|
export GIT_DIR="${REPOSITORY}/.git"
|
||||||
|
export GIT_WORK_TREE="${REPOSITORY}"
|
||||||
|
|
||||||
|
if [ -d "${GIT_DIR}" ]; then
|
||||||
|
main
|
||||||
|
else
|
||||||
|
echo "There is no Git repository in '${REPOSITORY}'" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
|
@ -1,3 +0,0 @@
|
||||||
#!/bin/sh
|
|
||||||
|
|
||||||
git --git-dir /etc/.git gc --quiet
|
|
|
@ -1,25 +1,47 @@
|
||||||
---
|
---
|
||||||
|
|
||||||
|
- name: "evocommit script is installed"
|
||||||
|
copy:
|
||||||
|
src: evocommit
|
||||||
|
dest: /usr/local/bin/evocommit
|
||||||
|
mode: "0755"
|
||||||
|
force: yes
|
||||||
|
|
||||||
|
# /etc
|
||||||
- name: Is /etc a git repository
|
- name: Is /etc a git repository
|
||||||
stat:
|
stat:
|
||||||
path: /etc/.git
|
path: /etc/.git
|
||||||
register: _etc_git
|
register: _etc_git
|
||||||
|
|
||||||
- include: do_commit.yml
|
- name: "evocommit /etc"
|
||||||
vars:
|
command: "/usr/local/bin/evocommit --repository /etc --message \"{{ commit_message | mandatory }}\""
|
||||||
git_folder: "/etc"
|
ignore_errors: yes
|
||||||
when:
|
when:
|
||||||
- _etc_git.stat.exists
|
- _etc_git.stat.exists
|
||||||
- _etc_git.stat.isdir
|
- _etc_git.stat.isdir
|
||||||
|
|
||||||
|
# /etc/bind
|
||||||
|
- name: Is /etc/bind a git repository
|
||||||
|
stat:
|
||||||
|
path: /etc/bind/.git
|
||||||
|
register: _etc_bind_git
|
||||||
|
|
||||||
|
- name: "evocommit /etc/bind"
|
||||||
|
command: "/usr/local/bin/evocommit --repository /etc/bind --message \"{{ commit_message | mandatory }}\""
|
||||||
|
ignore_errors: yes
|
||||||
|
when:
|
||||||
|
- _etc_bind_git.stat.exists
|
||||||
|
- _etc_bind_git.stat.isdir
|
||||||
|
|
||||||
|
# /usr/share/scripts
|
||||||
- name: Is /usr/share/scripts a git repository
|
- name: Is /usr/share/scripts a git repository
|
||||||
stat:
|
stat:
|
||||||
path: /usr/share/scripts/.git
|
path: /usr/share/scripts/.git
|
||||||
register: _usr_share_scripts_git
|
register: _usr_share_scripts_git
|
||||||
|
|
||||||
- include: do_commit.yml
|
- name: "evocommit /usr/share/scripts"
|
||||||
vars:
|
command: "/usr/local/bin/evocommit --repository /usr/share/scripts --message \"{{ commit_message | mandatory }}\""
|
||||||
git_folder: "/usr/share/scripts"
|
ignore_errors: yes
|
||||||
when:
|
when:
|
||||||
- _usr_share_scripts_git.stat.exists
|
- _usr_share_scripts_git.stat.exists
|
||||||
- _usr_share_scripts_git.stat.isdir
|
- _usr_share_scripts_git.stat.isdir
|
||||||
|
|
|
@ -1,77 +0,0 @@
|
||||||
---
|
|
||||||
|
|
||||||
- name: "Remount /usr if needed"
|
|
||||||
include_role:
|
|
||||||
name: remount-usr
|
|
||||||
when: git_folder is match('/usr/.*')
|
|
||||||
|
|
||||||
- name: "stat {{ git_folder }}/.git/index.lock"
|
|
||||||
stat:
|
|
||||||
path: "{{ git_folder }}/.git/index.lock"
|
|
||||||
register: _index_lock
|
|
||||||
|
|
||||||
- name: index file is removed if old enough
|
|
||||||
file:
|
|
||||||
path: "{{ git_folder }}/.git/index.lock"
|
|
||||||
state: absent
|
|
||||||
when:
|
|
||||||
- _index_lock.stat.exists
|
|
||||||
- _index_lock.stat.mtime | int <= ((ansible_date_time.epoch | int) - etc_git_purge_index_lock_age | default(86400))
|
|
||||||
- etc_git_purge_index_lock_enabled
|
|
||||||
|
|
||||||
- name: "is {{ git_folder }} clean?"
|
|
||||||
command: git status --porcelain
|
|
||||||
args:
|
|
||||||
chdir: "{{ git_folder }}"
|
|
||||||
changed_when: False
|
|
||||||
register: git_status
|
|
||||||
when: not ansible_check_mode
|
|
||||||
ignore_errors: yes
|
|
||||||
tags:
|
|
||||||
- etc-git
|
|
||||||
- commit
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: git_status
|
|
||||||
verbosity: 3
|
|
||||||
tags:
|
|
||||||
- etc-git
|
|
||||||
- commit
|
|
||||||
|
|
||||||
- name: fetch current Git user.email
|
|
||||||
git_config:
|
|
||||||
name: user.email
|
|
||||||
repo: "{{ git_folder }}"
|
|
||||||
register: git_config_user_email
|
|
||||||
ignore_errors: yes
|
|
||||||
tags:
|
|
||||||
- etc-git
|
|
||||||
- commit
|
|
||||||
|
|
||||||
- name: "set commit author"
|
|
||||||
set_fact:
|
|
||||||
commit_author: '{% if ansible_env.SUDO_USER is not defined %}root{% else %}{{ ansible_env.SUDO_USER }}{% endif %}'
|
|
||||||
commit_email: '{% if git_config_user_email.config_value is not defined or not git_config_user_email.config_value %}root@localhost{% else %}{{ git_config_user_email.config_value }}{% endif %}' # noqa 204
|
|
||||||
tags:
|
|
||||||
- etc-git
|
|
||||||
- commit
|
|
||||||
|
|
||||||
- name: "{{ git_folder }} modifications are committed"
|
|
||||||
shell: "git add -A . && git commit -m \"{{ commit_message | mandatory }}\" --author \"{{ commit_author | mandatory }} <{{ commit_email | mandatory }}>\""
|
|
||||||
args:
|
|
||||||
chdir: "{{ git_folder }}"
|
|
||||||
register: commit_end_run
|
|
||||||
when:
|
|
||||||
- not ansible_check_mode
|
|
||||||
- git_status.stdout | length > 0
|
|
||||||
ignore_errors: yes
|
|
||||||
tags:
|
|
||||||
- etc-git
|
|
||||||
- commit
|
|
||||||
|
|
||||||
- debug:
|
|
||||||
var: commit_end_run
|
|
||||||
verbosity: 4
|
|
||||||
tags:
|
|
||||||
- etc-git
|
|
||||||
- commit
|
|
|
@ -32,6 +32,33 @@
|
||||||
- _usr_share_scripts.stat.isdir
|
- _usr_share_scripts.stat.isdir
|
||||||
- ansible_distribution_major_version is version('10', '>=')
|
- ansible_distribution_major_version is version('10', '>=')
|
||||||
|
|
||||||
|
- name: "evocommit script is installed"
|
||||||
|
copy:
|
||||||
|
src: evocommit
|
||||||
|
dest: /usr/local/bin/evocommit
|
||||||
|
mode: "0755"
|
||||||
|
force: yes
|
||||||
|
tags:
|
||||||
|
- etc-git
|
||||||
|
|
||||||
|
- name: "etc-git-optimize script is installed"
|
||||||
|
copy:
|
||||||
|
src: etc-git-optimize
|
||||||
|
dest: /usr/share/scripts/etc-git-optimize
|
||||||
|
mode: "0755"
|
||||||
|
force: yes
|
||||||
|
tags:
|
||||||
|
- etc-git
|
||||||
|
|
||||||
|
- name: "etc-git-status script is installed"
|
||||||
|
copy:
|
||||||
|
src: etc-git-status
|
||||||
|
dest: /usr/share/scripts/etc-git-status
|
||||||
|
mode: "0755"
|
||||||
|
force: yes
|
||||||
|
tags:
|
||||||
|
- etc-git
|
||||||
|
|
||||||
- name: Check if cron is installed
|
- name: Check if cron is installed
|
||||||
shell: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'"
|
shell: "set -o pipefail && dpkg -l cron 2>/dev/null | grep -q -E '^(i|h)i'"
|
||||||
args:
|
args:
|
||||||
|
@ -41,29 +68,44 @@
|
||||||
check_mode: no
|
check_mode: no
|
||||||
register: is_cron_installed
|
register: is_cron_installed
|
||||||
|
|
||||||
- name: Optimize script is installed in monthly crontab
|
- block:
|
||||||
copy:
|
- name: Legacy cron jobs for /etc/.git status are absent
|
||||||
src: optimize-etc-git
|
file:
|
||||||
dest: /etc/cron.monthly/optimize-etc-git
|
dest: "{{ item }}"
|
||||||
mode: "0750"
|
state: absent
|
||||||
force: no
|
loop:
|
||||||
|
- /etc/cron.monthly/optimize-etc-git
|
||||||
|
- /etc/cron.d/etc-git-status
|
||||||
|
|
||||||
|
- name: Cron job for monthly git optimization
|
||||||
|
cron:
|
||||||
|
name: "Monthly optimization"
|
||||||
|
cron_file: etc-git
|
||||||
|
special_time: "monthly"
|
||||||
|
user: root
|
||||||
|
job: "/usr/share/scripts/etc-git-optimize"
|
||||||
|
|
||||||
|
- name: Cron job for hourly git status
|
||||||
|
cron:
|
||||||
|
name: "Hourly warning for unclean Git repository if nobody is connected"
|
||||||
|
cron_file: etc-git
|
||||||
|
special_time: "hourly"
|
||||||
|
user: root
|
||||||
|
job: "who > /dev/null || /usr/share/scripts/etc-git-status"
|
||||||
|
state: "{{ etc_git_monitor_status | bool | ternary('present','absent') }}"
|
||||||
|
|
||||||
|
- name: Cron job for daily git status
|
||||||
|
cron:
|
||||||
|
name: "Daily warning for unclean Git repository"
|
||||||
|
cron_file: etc-git
|
||||||
|
user: root
|
||||||
|
job: "/usr/share/scripts/etc-git-status"
|
||||||
|
minute: "21"
|
||||||
|
hour: "21"
|
||||||
|
weekday: "*"
|
||||||
|
day: "*"
|
||||||
|
month: "*"
|
||||||
|
state: "{{ etc_git_monitor_status | bool | ternary('present','absent') }}"
|
||||||
when: is_cron_installed.rc == 0
|
when: is_cron_installed.rc == 0
|
||||||
tags:
|
tags:
|
||||||
- etc-git
|
- etc-git
|
||||||
|
|
||||||
- name: Cron job for /etc/.git status is installed
|
|
||||||
template:
|
|
||||||
src: etc-git-status.j2
|
|
||||||
dest: /etc/cron.d/etc-git-status
|
|
||||||
mode: "0644"
|
|
||||||
when: is_cron_installed.rc == 0 and etc_git_monitor_status
|
|
||||||
tags:
|
|
||||||
- etc-git
|
|
||||||
|
|
||||||
- name: Cron job for /etc/.git status is removed
|
|
||||||
file:
|
|
||||||
dest: /etc/cron.d/etc-git-status
|
|
||||||
state: absent
|
|
||||||
when: is_cron_installed.rc == 0 and not etc_git_monitor_status
|
|
||||||
tags:
|
|
||||||
- etc-git
|
|
|
@ -1,4 +0,0 @@
|
||||||
# {{ ansible_managed }}
|
|
||||||
|
|
||||||
@hourly root who > /dev/null || git --git-dir=/etc/.git --work-tree=/etc status --short
|
|
||||||
21 21 * * * root git --git-dir=/etc/.git --work-tree=/etc status --short
|
|
Loading…
Reference in New Issue