name: 🚀 Deploy run-name: '🚀 Deploy: ${{ gitea.ref_name }} by @${{ gitea.actor }}' on: workflow_dispatch: jobs: deploy: runs-on: ubuntu-latest steps: - name: 📂 Checkout repository uses: actions/checkout@v4 - name: 🧼 Clean ref name run: | ORIGINAL_REF_NAME="${{ gitea.ref_name }}" CLEAN_REF_NAME=${ORIGINAL_REF_NAME//\//-} echo "Clean ref name: $CLEAN_REF_NAME" echo "CLEAN_REF_NAME=$CLEAN_REF_NAME" >> $GITEA_ENV - name: ⚙️ Configure ssh client uses: ./.gitea/actions/configure-ssh-client with: ssh_host: ${{ secrets.SSH_HOST }} - name: 📦 Create backup of data/ folder if: ${{ vars.DEPLOY_BACKUP_ENABLED == 'true' }} env: SSHPASS: ${{ secrets.SSH_PASSWORD }} SSH_USERNAME: ${{ secrets.SSH_USERNAME }} SSH_HOST: ${{ secrets.SSH_HOST }} DEPLOY_DIRECTORY: ${{ vars.DEPLOY_DIRECTORY }} REF_NAME: ${{ env.CLEAN_REF_NAME }} run: | sshpass -e ssh -p 22 "$SSH_USERNAME@$SSH_HOST" bash -s << EOT set -e cd "$DEPLOY_DIRECTORY" mkdir -p backups BACKUP_DATE=\$(date +%Y-%m-%d_%H-%M-%S) BACKUP_FILE="backups/backup-${REF_NAME}-\${BACKUP_DATE}.tar.gz" echo "Creating backup of data/ into \${BACKUP_FILE}..." if [ -d "data" ]; then echo "$SSHPASS" | sudo -S tar -czf "\${BACKUP_FILE}" data echo "✅ Backup created successfully!" else echo "⚡️ No data/ folder, backup creation skipped." fi echo "$SSHPASS" | sudo -S find backups/ -type f -name "backup-*.tar.gz" -mtime +7 -delete EOT - name: 🏗️ Create docker-compose file env: SSHPASS: ${{ secrets.SSH_PASSWORD }} SSH_USERNAME: ${{ secrets.SSH_USERNAME }} SSH_HOST: ${{ secrets.SSH_HOST }} DEPLOY_DIRECTORY: ${{ vars.DEPLOY_DIRECTORY }} REF_NAME: ${{ env.CLEAN_REF_NAME }} run: | sshpass -e ssh -p 22 "$SSH_USERNAME@$SSH_HOST" \ "mkdir -p \"$DEPLOY_DIRECTORY\" && \ cd \"$DEPLOY_DIRECTORY\" && \ rm -f docker-compose-*.yml && \ cat > \"docker-compose-$REF_NAME.yml\"" \ < docker-compose.yml - name: 🧩 Prepare configs env: MATRIX_SERVER_NAME: ${{ vars.MATRIX_SERVER_NAME }} MATRIX_PUBLIC_BASEURL: ${{ vars.MATRIX_PUBLIC_BASEURL }} ELEMENT_WEB_URL: ${{ vars.ELEMENT_WEB_URL }} SYNAPSE_POSTGRES_PASSWORD: ${{ secrets.SYNAPSE_POSTGRES_PASSWORD }} SYNAPSE_REGISTRATION_SHARED_SECRET: ${{ secrets.SYNAPSE_REGISTRATION_SHARED_SECRET }} run: | rm -rf .deploy python3 - << 'PY' import json import os from pathlib import Path replacements = { "MATRIX_SERVER_NAME": os.environ["MATRIX_SERVER_NAME"], "MATRIX_PUBLIC_BASEURL": os.environ["MATRIX_PUBLIC_BASEURL"].rstrip("/") + "/", "ELEMENT_WEB_LOCATION": os.environ["ELEMENT_WEB_URL"].rstrip("/") + "/", "SYNAPSE_POSTGRES_PASSWORD": os.environ["SYNAPSE_POSTGRES_PASSWORD"], "SYNAPSE_REGISTRATION_SHARED_SECRET": os.environ["SYNAPSE_REGISTRATION_SHARED_SECRET"], } def render(template_path, output_path): config = template_path.read_text(encoding="utf-8") for key, value in replacements.items(): config = config.replace(f"__{key}__", json.dumps(value)) output_path.parent.mkdir(parents=True, exist_ok=True) output_path.write_text(config, encoding="utf-8") render( Path("config/synapse/homeserver.yaml.template"), Path(".deploy/secrets/synapse_homeserver_config.yaml"), ) render( Path("config/element/config.json.template"), Path(".deploy/secrets/element_config.json"), ) PY find .deploy -type d -exec chmod 700 {} \; find .deploy -type f -exec chmod 644 {} \; - name: 🔐 Create env, secrets and config env: SSHPASS: ${{ secrets.SSH_PASSWORD }} SSH_USERNAME: ${{ secrets.SSH_USERNAME }} SSH_HOST: ${{ secrets.SSH_HOST }} DEPLOY_DIRECTORY: ${{ vars.DEPLOY_DIRECTORY }} BESZEL_AGENT_KEY: ${{ secrets.BESZEL_AGENT_KEY }} BESZEL_AGENT_TOKEN: ${{ secrets.BESZEL_AGENT_TOKEN }} BESZEL_HUB_URL: ${{ vars.BESZEL_HUB_URL }} GITEA_RUNNER_REGISTRATION_TOKEN: ${{ secrets.RUNNER_REGISTRATION_TOKEN }} GITEA_URL: ${{ vars.GIT_URL }} VAULTWARDEN_ADMIN_TOKEN: ${{ secrets.VAULTWARDEN_ADMIN_TOKEN }} NEXTCLOUD_DOMAIN: ${{ vars.NEXTCLOUD_DOMAIN }} RUSTDESK_DOMAIN: ${{ vars.RUSTDESK_DOMAIN }} POSTGRES_ADMIN_PASSWORD: ${{ secrets.POSTGRES_ADMIN_PASSWORD }} SYNAPSE_POSTGRES_PASSWORD: ${{ secrets.SYNAPSE_POSTGRES_PASSWORD }} run: | tar -C .deploy -czf - secrets | sshpass -e ssh -p 22 "$SSH_USERNAME@$SSH_HOST" \ "mkdir -p \"$DEPLOY_DIRECTORY\" && \ cd \"$DEPLOY_DIRECTORY\" && \ tar -xzf -" sshpass -e ssh -p 22 "$SSH_USERNAME@$SSH_HOST" bash -s << EOT set -e cd "$DEPLOY_DIRECTORY" cat << 'EOF_ENV' > .env BESZEL_HUB_URL=$BESZEL_HUB_URL NEXTCLOUD_DOMAIN=$NEXTCLOUD_DOMAIN GITEA_URL=$GITEA_URL RUSTDESK_DOMAIN=$RUSTDESK_DOMAIN EOF_ENV mkdir -p secrets cat << 'EOF_SECRET' > secrets/beszel_agent_key.txt $BESZEL_AGENT_KEY EOF_SECRET cat << 'EOF_SECRET' > secrets/beszel_agent_token.txt $BESZEL_AGENT_TOKEN EOF_SECRET cat << 'EOF_SECRET' > secrets/gitea_runner_registration_token.txt $GITEA_RUNNER_REGISTRATION_TOKEN EOF_SECRET cat << 'EOF_SECRET' > secrets/vaultwarden_admin_token.txt $VAULTWARDEN_ADMIN_TOKEN EOF_SECRET cat << 'EOF_SECRET' > secrets/postgres_admin_password.txt $POSTGRES_ADMIN_PASSWORD EOF_SECRET cat << 'EOF_SECRET' > secrets/synapse_postgres_password.txt $SYNAPSE_POSTGRES_PASSWORD EOF_SECRET chmod 700 secrets chmod 600 secrets/*.txt chmod 644 secrets/synapse_homeserver_config.yaml secrets/element_config.json EOT - name: 🗄️ Initialize databases env: SSHPASS: ${{ secrets.SSH_PASSWORD }} SSH_USERNAME: ${{ secrets.SSH_USERNAME }} SSH_HOST: ${{ secrets.SSH_HOST }} DEPLOY_DIRECTORY: ${{ vars.DEPLOY_DIRECTORY }} REF_NAME: ${{ env.CLEAN_REF_NAME }} DRY_RUN_ENABLED: ${{ vars.DEPLOY_DRY_RUN_ENABLED }} run: | if [ "${DRY_RUN_ENABLED}" = "true" ]; then echo "Dry run enabled, database initialization skipped." exit 0 fi sshpass -e ssh -p 22 "$SSH_USERNAME@$SSH_HOST" bash -s << EOT set -e cd "$DEPLOY_DIRECTORY" docker compose -f "docker-compose-${REF_NAME}.yml" pull db docker compose -f "docker-compose-${REF_NAME}.yml" up -d db for attempt in \$(seq 1 30); do if docker compose -f "docker-compose-${REF_NAME}.yml" exec -T db pg_isready -U postgres; then break fi if [ "\${attempt}" -eq 30 ]; then echo "PostgreSQL is not ready after 30 attempts." exit 1 fi sleep 2 done EOT sshpass -e ssh -p 22 "$SSH_USERNAME@$SSH_HOST" \ "cd \"$DEPLOY_DIRECTORY\" && \ docker run --rm -i \ --entrypoint /bin/sh \ --network vbevdev \ -e PGHOST=db \ -e PGPORT=5432 \ -e PGUSER=postgres \ -e PGPASSWORD_FILE=/run/secrets/postgres_admin_password.txt \ -e SYNAPSE_POSTGRES_PASSWORD_FILE=/run/secrets/synapse_postgres_password.txt \ -v \"$DEPLOY_DIRECTORY/secrets:/run/secrets:ro\" \ postgres:16-alpine \ -s" < ops/postgres/init-services.sh - name: 🚀 Run new containers env: SSHPASS: ${{ secrets.SSH_PASSWORD }} SSH_USERNAME: ${{ secrets.SSH_USERNAME }} SSH_HOST: ${{ secrets.SSH_HOST }} DEPLOY_DIRECTORY: ${{ vars.DEPLOY_DIRECTORY }} DEPLOY_LOG_FILE: /tmp/deploy-${{ env.CLEAN_REF_NAME }}.log DRY_RUN_ENABLED: ${{ vars.DEPLOY_DRY_RUN_ENABLED }} REF_NAME: ${{ env.CLEAN_REF_NAME }} TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_DEPLOY_BOT_TOKEN }} TELEGRAM_CHAT: ${{ secrets.TELEGRAM_DEPLOY_CHAT_ID }} PROXY_URL: ${{ secrets.PROXY_URL }} SUCCESS_MESSAGE: |- ✅ Успешный деплой! Релиз: ${{ gitea.ref_name }} FAILURE_MESSAGE: |- ❌ Ошибка деплоя! Релиз: ${{ gitea.ref_name }} run: | sshpass -e ssh -p 22 "$SSH_USERNAME@$SSH_HOST" bash -s << EOT set -e cd "$DEPLOY_DIRECTORY" nohup bash -c ' sleep 5 if [ "${DRY_RUN_ENABLED}" = "true" ]; then DRY_RUN_FLAG="--dry-run" else DRY_RUN_FLAG="" fi if docker compose \${DRY_RUN_FLAG} -f "docker-compose-${REF_NAME}.yml" pull > ${DEPLOY_LOG_FILE} 2>&1 && \ docker compose \${DRY_RUN_FLAG} -f "docker-compose-${REF_NAME}.yml" up -d --remove-orphans >> ${DEPLOY_LOG_FILE} 2>&1; then curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \ --proxy "${PROXY_URL}" \ -d "chat_id=${TELEGRAM_CHAT}" \ -d "parse_mode=HTML" \ --data-urlencode "text=${SUCCESS_MESSAGE}" > /dev/null else curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \ --proxy "${PROXY_URL}" \ -d "chat_id=${TELEGRAM_CHAT}" \ -d "parse_mode=HTML" \ --data-urlencode "text=${FAILURE_MESSAGE}" > /dev/null fi curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendDocument" \ --proxy "${PROXY_URL}" \ -F "chat_id=${TELEGRAM_CHAT}" \ -F "document=@${DEPLOY_LOG_FILE}" > /dev/null && rm -f ${DEPLOY_LOG_FILE} ' > /dev/null 2>&1 & EOT echo "New containers will be started after 5 seconds"