commit 5c97cd9c8a5e4add46b369b0464a2a2610a5dc6a Author: shmick Date: Sat Nov 18 11:38:01 2023 +0200 Initial Commit - Hatarashi Hako with working Actions diff --git a/.gitea/workflows/hatarashi-hako.yaml b/.gitea/workflows/hatarashi-hako.yaml new file mode 100644 index 0000000..e985224 --- /dev/null +++ b/.gitea/workflows/hatarashi-hako.yaml @@ -0,0 +1,31 @@ +name: Ansible Deploy + +on: + push: + +jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - name: Checkout Repository + uses: actions/checkout@v3 + + - name: Set up Python + run: | + apt -y update + apt -y install python3 python3-pip skopeo + + - name: Install Ansible + run: | + python3 -m pip install --upgrade pip + pip install ansible + + - name: Set up SSH + uses: webfactory/ssh-agent@v0.5.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Run Ansible Playbook + run: | + ansible-playbook -i inventory.yaml -e 'ANSIBLE_SUDO_PASS="${{ secrets.SUDO_PASS }}" SKOPEO_PASS="${{ secrets.SKOPEO_PASS }}"' hatarashi-hako.yaml -vv diff --git a/hatarashi-hako.service.j2 b/hatarashi-hako.service.j2 new file mode 100644 index 0000000..ce6e8d1 --- /dev/null +++ b/hatarashi-hako.service.j2 @@ -0,0 +1,8 @@ +[Unit] +Description=Hatarashi Hako - Container update utility + +[Service] +ExecStart=/bin/bash /usr/local/bin/hatarashi-hako.sh -p {{ SKOPEO_PASS }} + +[Install] +WantedBy=multi-user.target diff --git a/hatarashi-hako.sh b/hatarashi-hako.sh new file mode 100755 index 0000000..b8169b9 --- /dev/null +++ b/hatarashi-hako.sh @@ -0,0 +1,143 @@ +#!/bin/bash +##Script to update Docker container images occasionally and alert when update is done. + +#This is where containers live +CONTAINER_DIR="/var/Red-Vol/Media/Containers/" + + +show_help() +{ + echo "Hatarashi Hako - Smartly update container stack" + echo " {-s|--stack} [name] -- Run update process just for specified stack (ex: vikunja)" + echo " {-h|--help} -- Print this help message and exit" + exit 0 +} +#Pass arguments to the script +flags() +{ + while test $# -gt 0 + do + case "$1" in + #If a stack is specified, run the process for that stack only + (-s|--stack) + shift + if [[ -d $CONTAINER_DIR/$1 ]]; then + export CONTAINER_PATHS="$CONTAINER_DIR/$1" + else + CONTAINER_PATHS="$(find /var/Red-Vol/Media/Containers/ -maxdepth 1 -type d -name "*$1*" | head -1)" + if [[ -n $CONTAINER_PATHS ]]; then + export CONTAINER_PATHS + fi + fi + shift;; + (-p|--password) + shift + export PASS="$1" + shift;; + (-h|--help) + show_help;; + (*) show_help;; + esac + done +} +flags "$@" + + +#Remember where you are to change back to later +LOCAL_DIR=`pwd` + +#File to write results to; picked up by Prometheus and yells about changes +PROM_FILE="$CONTAINER_DIR/prometheus/data/hatarashi-hako.prom" + +#Remove log from last runs, if present +if [[ -f $PROM_FILE ]]; then + rm $PROM_FILE +fi + +#Check if path is already set by user specified stack; otherwise, find all containers. +if [[ -z $CONTAINER_PATHS ]]; then + CONTAINER_PATHS=$(find $CONTAINER_DIR -maxdepth 2 -type f -name docker-compose.yml ! -path '*Archive*' | xargs dirname ) +fi + +#Find containers in ^ base dir ^ in base container path ^ by finding compose files ^ (not here) ^ and getting their directory name. +for container_path in ${CONTAINER_PATHS[@]}; do + cd $container_path + echo -e "Working on container directory" "$container_path" + container_stack=$(basename $container_path) + echo -e "Working on stack" "$container_stack" + #It's deadly to update tagless database images; this line is safe because it only catches tagged images. + container_images="$(cat $container_path/docker-compose.yml | grep -E "image: ([a-z]+)((/)|(:))([a-z]+)?(:)?([a-z0-9].*$)?" | awk '{print $2}')" +# search for a pattern of something:something with optional :tag print ^ image name + for container_image in $container_images; do + echo -e "$container_stack has image" "$container_image" + echo -e "echo $container_image | awk -F/ '{print \$2}' | sed 's/\:.*//'" + container_name="$(echo $container_image | awk -F/ '{print $2}' | sed "s/\:.*//")" +# remove everything after the : ^ + if [[ -z $container_name ]]; then #&& [[ -n $(echo $container_image | grep -Ev 'postgres|mariadb') ]]; then + export container_name="$container_image" + fi + echo -e "$container_image has name" "$container_name" + if [[ -n $(echo $container_image | grep -E "(.*:[a-z0-9].*$)") ]]; then +# check if there is a :tag present ^ + image_tag=":$(echo $container_image | awk -F: '{print $NF}')" +# !! Add : ^ before image !! so it is only added to later commands if there is an image at all + echo -e "$container_image has tag" "$image_tag" + export container_image=$(echo $container_image | awk -F: '{print $1}') +# If the container does have a tag, keep the base name ^ without it (before the :) + export container_name=$(echo $container_name | awk -F: '{print $1}') + fi + echo -e "Fetching local image checksum with:" "docker inspect \"$container_image$image_tag\" | grep -Eo \"($container_image@)?sha256:([0-9a-zA-Z].*)(\\\")\" | sed -e 's/\"//g' | awk -F@ '{print \$2}" + local_image=$(docker inspect "$container_image$image_tag" | grep -Eo "($container_image@)?sha256:([0-9a-zA-Z].*)(\")" | sed -e 's/"//g' -e 's/\s+//g' | awk -F@ '{print $2}') +# remember, this bit ^ is empty without an image ^ this is the main image checksum remove ^ " and whitespace and^ get the checksum after the @ + if [[ -z $local_image ]]; then + echo -e "Error fetching local image checksum for container $container_name!" + #The script will complain about failed containers later on + echo "container_updated{name=\"$container_name\"} -1" >> $PROM_FILE + continue 2 + else + echo -e "Local SHA256 for $container_image is" "$local_image" + fi + echo -e "Fetching remote image with:" "skopeo inspect --creds \"dkd6:$PASS\" docker://docker.io/$container_image$image_tag | grep Digest | head -1 | grep -Eo 'sha256:([0-9a-zA-Z].*)(\")' | sed -e 's/\"//g'" + #Use Skopeo, a Red Hat tool, with my Docker Hub account to register the remote image checksum + remote_image=$(skopeo inspect --creds "dkd6:$PASS" docker://docker.io/$container_image$image_tag | grep Digest | head -1 | grep -Eo 'sha256:([0-9a-zA-Z].*)(")' | sed -e 's/"//g' -e 's/\s+//g' ) + #Sometimes; Docker hub hangs up; try again if you failed + if [[ -z $remote_image ]]; then + remote_image=$(skopeo inspect --creds "dkd6:$PASS" docker://docker.io/$container_image$image_tag | grep Digest | head -1 | grep -Eo 'sha256:([0-9a-zA-Z].*)(")' | sed -e 's/"//g') + fi + #Now, if you still don't have an image after the second try, something's fuckey. + if [[ -z $remote_image ]]; then + echo -e "Error fetching remote image checksum for container" "$container_name!" + echo "container_updated{name=\"$container_name\"} -1" >> $PROM_FILE + continue 2 + else + echo -e "Remote SHA256 for $container_image is" "$remote_image" + fi + #If we have both checksums, compare them; they should be identical, or the container is outdated. + if [[ -n $local_image ]] && [[ -n $remote_image ]] && [[ "$local_image" =~ "$remote_image" ]]; then + echo -e "$container_name" "is up to date!" + else + echo -e "$container_name" "is out of date!" + echo -e "cat \"$container_path/docker-compose.yml\" | grep -B1 \"image: $container_image\" | head -1 | sed -e 's/^[ \t]*//' -e 's/://g' | awk '{print \$NF}')" + service=$(cat "$container_path/docker-compose.yml" | grep -B1 "image: $container_image" | head -1 | sed -e 's/^[ \t]*//' -e 's/://g' | grep -v 'container_name') + # get container service name (1 line above image) ^ print service name^ ^ omit tabs and : and ^omit container_name + echo -e "Attempting to update service" "$service" + if docker compose pull $service; then + echo -e "Pulled latest image for" "$container_name" + if docker compose up -d --remove-orphans; then + echo -e "$container_stack" "has been updated sucessfully!" + echo "container_updated{name=\"$container_name\"} 1" >> $PROM_FILE + else + echo -e "Failed to update" "$container_name!" + echo "container_updated{name=\"$container_name\"} 0" >> $PROM_FILE + fi + else + echo -e "Failed to pull image for" "$container_name!" + echo "container_updated{name=\"$container_name\"} 0" >> $PROM_FILE + fi + fi + #If you found an image tag, reset it before moving on to another container + image_tag="" + done + cd $LOCAL_DIR +done +echo "All done!" diff --git a/hatarashi-hako.timer.j2 b/hatarashi-hako.timer.j2 new file mode 100644 index 0000000..0610e64 --- /dev/null +++ b/hatarashi-hako.timer.j2 @@ -0,0 +1,9 @@ +[Unit] +Description=Hatarashi Hako - container update utility + +[Timer] +OnCalendar=daily +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/hatarashi-hako.yaml b/hatarashi-hako.yaml new file mode 100644 index 0000000..3d993c6 --- /dev/null +++ b/hatarashi-hako.yaml @@ -0,0 +1,32 @@ +- hosts: takahe + gather_facts: no + become: yes + vars: + ansible_ssh_common_args: '-o StrictHostKeyChecking=no' + ansible_sudo_pass: "{{ ANSIBLE_SUDO_PASS }}" + + tasks: + - name: Install Skopeo + ansible.builtin.package: + name: skopeo + state: latest + + - name: Install hatarashi-hako + ansible.builtin.copy: + src: hatarashi-hako.sh + dest: /usr/local/bin/hatarashi-hako.sh + mode: 'a+x' + + - name: Template service and timer to host + ansible.builtin.template: + src: "{{ item }}.j2" + dest: /etc/systemd/system/{{ item }} + with_items: + - hatarashi-hako.service + - hatarashi-hako.timer + + - name: Start hatarashi-hako timer + ansible.builtin.systemd: + name: hatarashi-hako.timer + state: started + daemon_reload: true diff --git a/inventory.yaml b/inventory.yaml new file mode 100644 index 0000000..db5f513 --- /dev/null +++ b/inventory.yaml @@ -0,0 +1,5 @@ +all: + hosts: + takahe: + ansible_host: 192.168.0.66 + ansible_user: shmick