From dc76e48e21c0b3974bcb5a6aa4365b889e773559 Mon Sep 17 00:00:00 2001 From: Dmitry Date: Wed, 27 May 2026 21:46:38 +0300 Subject: [PATCH] Refactor deployment workflows and enhance Docker configuration management --- .gitea/workflows/deploy-cloud-pc.yml | 52 +++----------- .gitea/workflows/deploy-mini-pc.yml | 51 +++----------- .gitea/workflows/deploy-ru-vps.yml | 51 +++----------- ansible/roles/bootstrap/defaults/main.yml | 3 + ansible/roles/bootstrap/tasks/main.yml | 25 +++++++ cloud-pc/watchtower/docker-compose.yml | 2 +- scripts/deploy.sh | 86 +++++++++++++++++++++++ 7 files changed, 146 insertions(+), 124 deletions(-) create mode 100755 scripts/deploy.sh diff --git a/.gitea/workflows/deploy-cloud-pc.yml b/.gitea/workflows/deploy-cloud-pc.yml index 6d44364..e876dc9 100644 --- a/.gitea/workflows/deploy-cloud-pc.yml +++ b/.gitea/workflows/deploy-cloud-pc.yml @@ -4,53 +4,23 @@ on: workflow_dispatch: inputs: stack: - description: Stack directory to deploy - required: true + description: "Stack to deploy (leave empty to reconcile all)" + required: false push: paths: - '.gitea/workflows/deploy-cloud-pc.yml' - 'cloud-pc/**' + schedule: + - cron: '0 4 * * *' + jobs: deploy: runs-on: cloud-pc steps: - - name: Deploy stacks + - name: Sync repo run: | - set -euxo pipefail - cd /opt/services - git --version - docker version - docker compose version - git config --global --add safe.directory /opt/services - git pull - if [ -n "${{ inputs.stack }}" ]; then - dirs="${{ inputs.stack }}" - else - git --no-pager diff --name-only HEAD~1 HEAD - dirs="$(git --no-pager diff --name-only HEAD~1 HEAD | grep '^cloud-pc/' | cut -d/ -f2 | sort -u || true)" - fi - if [ -z "$dirs" ]; then - echo "No changed cloud-pc stacks" - exit 0 - fi - for dir in $dirs; do - echo "==> Deploying $dir" - if [ "$dir" = "gitea-runner" ]; then - echo "Skipping gitea-runner self-update" - continue - fi - cd /opt/services/cloud-pc/$dir - test -f docker-compose.yml || { echo "No docker-compose.yml in $dir, skipping"; cd /opt/services; continue; } - docker compose config --quiet - if grep -qE '^[[:space:]]*build:' docker-compose.yml; then - echo "==> Running docker compose up -d --build --remove-orphans for $dir" - docker compose up -d --build --remove-orphans - else - echo "==> Running docker compose pull for $dir" - docker compose pull - echo "==> Running docker compose up -d --remove-orphans for $dir" - docker compose up -d --remove-orphans - fi - docker compose ps - cd /opt/services - done + git config --global --replace-all safe.directory /opt/services + git -C /opt/services pull --ff-only + + - name: Deploy stacks + run: /opt/services/scripts/deploy.sh cloud-pc "${{ inputs.stack }}" diff --git a/.gitea/workflows/deploy-mini-pc.yml b/.gitea/workflows/deploy-mini-pc.yml index d46fed5..a105e7d 100644 --- a/.gitea/workflows/deploy-mini-pc.yml +++ b/.gitea/workflows/deploy-mini-pc.yml @@ -4,54 +4,23 @@ on: workflow_dispatch: inputs: stack: - description: Stack directory to deploy - required: true + description: "Stack to deploy (leave empty to reconcile all)" + required: false push: paths: - '.gitea/workflows/deploy-mini-pc.yml' - 'mini-pc/**' + schedule: + - cron: '0 4 * * *' jobs: deploy: runs-on: mini-pc steps: - - name: Deploy stacks + - name: Sync repo run: | - set -euxo pipefail - cd /opt/services - git --version - docker version - docker compose version - git config --global --add safe.directory /opt/services - git pull - if [ -n "${{ inputs.stack }}" ]; then - dirs="${{ inputs.stack }}" - else - git --no-pager diff --name-only HEAD~1 HEAD - dirs="$(git --no-pager diff --name-only HEAD~1 HEAD | grep '^mini-pc/' | cut -d/ -f2 | sort -u || true)" - fi - if [ -z "$dirs" ]; then - echo "No changed mini-pc stacks" - exit 0 - fi - for dir in $dirs; do - echo "==> Deploying $dir" - if [ "$dir" = "gitea-runner" ]; then - echo "Skipping gitea-runner self-update" - continue - fi - cd /opt/services/mini-pc/$dir - test -f docker-compose.yml || { echo "No docker-compose.yml in $dir, skipping"; cd /opt/services; continue; } - docker compose config --quiet - if grep -qE '^[[:space:]]*build:' docker-compose.yml; then - echo "==> Running docker compose up -d --build --remove-orphans for $dir" - docker compose up -d --build --remove-orphans - else - echo "==> Running docker compose pull for $dir" - docker compose pull - echo "==> Running docker compose up -d --remove-orphans for $dir" - docker compose up -d --remove-orphans - fi - docker compose ps - cd /opt/services - done + git config --global --replace-all safe.directory /opt/services + git -C /opt/services pull --ff-only + + - name: Deploy stacks + run: /opt/services/scripts/deploy.sh mini-pc "${{ inputs.stack }}" diff --git a/.gitea/workflows/deploy-ru-vps.yml b/.gitea/workflows/deploy-ru-vps.yml index 68997e0..1765c12 100644 --- a/.gitea/workflows/deploy-ru-vps.yml +++ b/.gitea/workflows/deploy-ru-vps.yml @@ -4,54 +4,23 @@ on: workflow_dispatch: inputs: stack: - description: Stack directory to deploy - required: true + description: "Stack to deploy (leave empty to reconcile all)" + required: false push: paths: - '.gitea/workflows/deploy-ru-vps.yml' - 'ru-vps/**' + schedule: + - cron: '0 4 * * *' jobs: deploy: runs-on: ru-vps steps: - - name: Deploy stacks + - name: Sync repo run: | - set -euxo pipefail - cd /opt/services - git --version - docker version - docker compose version - git config --global --add safe.directory /opt/services - git pull - if [ -n "${{ inputs.stack }}" ]; then - dirs="${{ inputs.stack }}" - else - git --no-pager diff --name-only HEAD~1 HEAD - dirs="$(git --no-pager diff --name-only HEAD~1 HEAD | grep '^ru-vps/' | cut -d/ -f2 | sort -u || true)" - fi - if [ -z "$dirs" ]; then - echo "No changed ru-vps stacks" - exit 0 - fi - for dir in $dirs; do - echo "==> Deploying $dir" - if [ "$dir" = "gitea-runner" ]; then - echo "Skipping gitea-runner self-update" - continue - fi - cd /opt/services/ru-vps/$dir - test -f docker-compose.yml || { echo "No docker-compose.yml in $dir, skipping"; cd /opt/services; continue; } - docker compose config --quiet - if grep -qE '^[[:space:]]*build:' docker-compose.yml; then - echo "==> Running docker compose up -d --build --remove-orphans for $dir" - docker compose up -d --build --remove-orphans - else - echo "==> Running docker compose pull for $dir" - docker compose pull - echo "==> Running docker compose up -d --remove-orphans for $dir" - docker compose up -d --remove-orphans - fi - docker compose ps - cd /opt/services - done + git config --global --replace-all safe.directory /opt/services + git -C /opt/services pull --ff-only + + - name: Deploy stacks + run: /opt/services/scripts/deploy.sh ru-vps "${{ inputs.stack }}" diff --git a/ansible/roles/bootstrap/defaults/main.yml b/ansible/roles/bootstrap/defaults/main.yml index a58a0ba..7a6e8d5 100644 --- a/ansible/roles/bootstrap/defaults/main.yml +++ b/ansible/roles/bootstrap/defaults/main.yml @@ -1,2 +1,5 @@ bootstrap_user: ada bootstrap_ssh_public_keys: [ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIWNsUQmppB6cQccXX1ZaBbFIcmM6RmghsTVbG9TgoZB ada@ada-x1] +docker_registry_mirrors: + - https://mirror.gcr.io + - https://dockerhub.timeweb.cloud diff --git a/ansible/roles/bootstrap/tasks/main.yml b/ansible/roles/bootstrap/tasks/main.yml index 3b8f68a..b8dcf1f 100644 --- a/ansible/roles/bootstrap/tasks/main.yml +++ b/ansible/roles/bootstrap/tasks/main.yml @@ -18,6 +18,31 @@ loop: "{{ bootstrap_ssh_public_keys }}" when: bootstrap_ssh_public_keys | length > 0 +- name: Gather service facts + ansible.builtin.service_facts: + +- name: Ensure Docker config directory exists + ansible.builtin.file: + path: /etc/docker + state: directory + mode: "0755" + +- name: Configure Docker registry mirrors + ansible.builtin.copy: + dest: /etc/docker/daemon.json + content: "{{ {'registry-mirrors': docker_registry_mirrors} | to_nice_json }}\n" + mode: "0644" + register: docker_daemon_config + when: docker_registry_mirrors | length > 0 + +- name: Restart Docker after daemon config change + ansible.builtin.service: + name: docker + state: restarted + when: + - docker_daemon_config is changed + - "'docker.service' in ansible_facts.services" + # Команды, которые реально нужны без пароля: # apt / apt-get — обновление пакетов # systemctl — управление сервисами diff --git a/cloud-pc/watchtower/docker-compose.yml b/cloud-pc/watchtower/docker-compose.yml index a253272..9d50a66 100644 --- a/cloud-pc/watchtower/docker-compose.yml +++ b/cloud-pc/watchtower/docker-compose.yml @@ -5,5 +5,5 @@ services: - DOCKER_API_VERSION=1.54 volumes: - /var/run/docker.sock:/var/run/docker.sock - command: --interval 300 --cleanup + command: --interval 300 --cleanup --label-enable restart: unless-stopped \ No newline at end of file diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..e78907d --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,86 @@ +#!/usr/bin/env bash +# Usage: deploy.sh [stack] +# host — node directory (cloud-pc, mini-pc, ru-vps) +# stack — optional single stack name; omit to reconcile all +set -euxo pipefail + +HOST="$1" +STACK="${2:-}" +BASE="/opt/services" + +cd "$BASE" + +if [ -n "$STACK" ]; then + dirs="$STACK" +else + dirs="$(find "$HOST" -mindepth 2 -maxdepth 2 -name docker-compose.yml -printf '%h\n' | cut -d/ -f2 | sort -u)" + + # Remove stale stacks whose directories were deleted from the repo + stale_dirs="$(docker ps -a \ + --filter "label=com.docker.compose.project.working_dir" \ + --format '{{.Label "com.docker.compose.project.working_dir"}}' \ + | sort -u \ + | grep "^$BASE/$HOST/" || true)" + + for working_dir in $stale_dirs; do + dir="${working_dir#"$BASE/$HOST/"}" + dir="${dir%%/*}" + if [ ! -f "$BASE/$HOST/$dir/docker-compose.yml" ]; then + echo "==> Removing stale stack: $dir" + containers="$(docker ps -aq --filter "label=com.docker.compose.project.working_dir=$working_dir" || true)" + [ -n "$containers" ] && docker rm -f $containers + networks="$(docker network ls -q --filter "label=com.docker.compose.project.working_dir=$working_dir" || true)" + [ -n "$networks" ] && docker network rm $networks || true + fi + done +fi + +if [ -z "$dirs" ]; then + echo "No stacks found for $HOST" + exit 0 +fi + +failed="" + +for dir in $dirs; do + echo "==> Stack: $dir" + + if [ "$dir" = "gitea-runner" ]; then + echo "Skipping gitea-runner (self-update not safe from CI)" + continue + fi + + stack_dir="$BASE/$HOST/$dir" + cd "$stack_dir" + + if [ ! -f docker-compose.yml ]; then + echo "No docker-compose.yml, skipping" + cd "$BASE" + continue + fi + + docker compose config --quiet + + if grep -qE '^[[:space:]]*build:' docker-compose.yml; then + docker compose up -d --build --remove-orphans + else + docker compose pull + docker compose up -d --remove-orphans + fi + + # Health check: give containers a moment, then fail on crashed services + sleep 5 + docker compose ps + if docker compose ps 2>&1 | grep -qE 'Exited \([1-9]|Restarting'; then + docker compose logs --tail=30 + echo "ERROR: Service failure in stack $dir" + failed="$failed $dir" + fi + + cd "$BASE" +done + +if [ -n "$failed" ]; then + echo "FAILED stacks:$failed" + exit 1 +fi