9 Commits
Author SHA1 Message Date
austnv f8b765dd99 fix build
Build and Deploy Backend / build-and-deploy (push) Failing after 2m1s
2026-06-26 13:13:37 +03:00
austnv 076d255656 fix build 2026-06-26 13:13:17 +03:00
austnv 6d543361c4 fix: add step "Set repository name to lowercase"
Build and Deploy Backend / build-and-deploy (push) Failing after 32s
2026-06-26 13:10:32 +03:00
austnv c17eb9584b fix: add docker push step to build.yml
Build and Deploy Backend / build-and-deploy (push) Failing after 32s
2026-06-26 13:06:13 +03:00
austnv a3c7b5bdd1 v0.7.5
Build and Deploy Backend / build-and-deploy (push) Successful in 33s
2026-06-26 12:55:38 +03:00
austnv 6c7adb1028 feat: add country_code field to hosts response 2026-06-26 12:51:45 +03:00
austnv 3661882c05 fix: volume data/tariffs.json -> data/
Build and Deploy Backend / build-and-deploy (push) Successful in 46s
2026-06-26 12:40:55 +03:00
austnv abcdb040ef fix: release author 2026-06-21 22:43:36 +03:00
austnv 718b9deebe fix: delete docker image from workflow file 2026-06-21 22:41:01 +03:00
5 changed files with 68 additions and 23 deletions
+39 -20
View File
@@ -11,15 +11,14 @@ jobs:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
# 1. Клонируем код
- name: Checkout code - name: Checkout code
uses: actions/checkout@v4 uses: actions/checkout@v4
with:
fetch-depth: 0
# 2. Настраиваем Docker Buildx
- name: Set up Docker Buildx - name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3 uses: docker/setup-buildx-action@v3
# 3. Логинимся в Container Registry Gitea
- name: Login to Gitea Container Registry - name: Login to Gitea Container Registry
uses: docker/login-action@v3 uses: docker/login-action@v3
with: with:
@@ -27,27 +26,53 @@ jobs:
username: ${{ gitea.repository_owner }} username: ${{ gitea.repository_owner }}
password: ${{ secrets.ACCESS_TOKEN }} password: ${{ secrets.ACCESS_TOKEN }}
# Преобразуем имена в нижний регистр и собираем метаданные
- name: Prepare metadata
id: meta
run: |
OWNER=$(echo "${{ gitea.repository_owner }}" | tr '[:upper:]' '[:lower:]')
REPO=$(echo "${{ github.event.repository.name }}" | tr '[:upper:]' '[:lower:]')
echo "owner=$OWNER" >> $GITHUB_OUTPUT
echo "repo=$REPO" >> $GITHUB_OUTPUT
echo "image=git.uvpn.shop/$OWNER/$REPO" >> $GITHUB_OUTPUT
echo "source=https://git.uvpn.shop/${{ gitea.repository }}" >> $GITHUB_OUTPUT
# Формируем дату
echo "build_date=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_OUTPUT
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: |
${{ steps.meta.outputs.image }}:latest
${{ steps.meta.outputs.image }}:${{ gitea.ref_name }}
cache-from: type=gha
cache-to: type=gha,mode=max
build-args: |
BUILD_VERSION=${{ gitea.ref_name }}
BUILD_DATE=${{ steps.meta.outputs.build_date }}
VCS_REF=${{ github.sha }}
GITEA_REPOSITORY=${{ steps.meta.outputs.source }}
- name: Create Gitea Release via API - name: Create Gitea Release via API
run: | run: |
# Получаем описание из тега (если есть)
TAG_MESSAGE=$(git tag -l --format='%(contents)' "${{ gitea.ref_name }}" 2>/dev/null || echo "Автоматический релиз через CI/CD") TAG_MESSAGE=$(git tag -l --format='%(contents)' "${{ gitea.ref_name }}" 2>/dev/null || echo "Автоматический релиз через CI/CD")
# Экранируем кавычки и переносы для JSON
TAG_MESSAGE_ESCAPED=$(echo "$TAG_MESSAGE" | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}') TAG_MESSAGE_ESCAPED=$(echo "$TAG_MESSAGE" | sed 's/"/\\"/g' | awk '{printf "%s\\n", $0}')
# Формируем JSON
JSON_PAYLOAD=$(cat <<EOF JSON_PAYLOAD=$(cat <<EOF
{ {
"tag_name": "${{ gitea.ref_name }}", "tag_name": "${{ gitea.ref_name }}",
"name": "Release ${{ gitea.ref_name }}", "name": "Release ${{ gitea.ref_name }}",
"body": "## Релиз ${{ gitea.ref_name }}\n\n📝 ${TAG_MESSAGE_ESCAPED}\n\n---\n\n**🐳 Docker образ:** \`git.uvpn.shop/${{ gitea.repository }}:${{ gitea.sha }}\`\n**🏷️ Тег:** \`${{ gitea.ref_name }}\`\n**👤 Автор:** ${{ gitea.actor }}\n\n✅ Автоматически собрано и развёрнуто", "body": "## Релиз ${{ gitea.ref_name }}\n\n📝 ${TAG_MESSAGE_ESCAPED}\n\n---\n\n**🏷️ Тег:** \`${{ gitea.ref_name }}\`\n**👤 Автор:** @${{ gitea.actor }}\n**📦 Образ:** \`${{ steps.meta.outputs.image }}:${{ gitea.ref_name }}\`\n\n✅ Автоматически собрано и развёрнуто",
"draft": false, "draft": false,
"prerelease": false "prerelease": false
} }
EOF EOF
) )
# Отправляем запрос
RESPONSE=$(curl -s -w "\n%{http_code}" \ RESPONSE=$(curl -s -w "\n%{http_code}" \
-X POST \ -X POST \
-H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" \ -H "Authorization: token ${{ secrets.ACCESS_TOKEN }}" \
@@ -55,7 +80,6 @@ jobs:
-d "$JSON_PAYLOAD" \ -d "$JSON_PAYLOAD" \
"https://git.uvpn.shop/api/v1/repos/${{ gitea.repository }}/releases") "https://git.uvpn.shop/api/v1/repos/${{ gitea.repository }}/releases")
# Проверяем ответ
HTTP_CODE=$(echo "$RESPONSE" | tail -n1) HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d') BODY=$(echo "$RESPONSE" | sed '$d')
@@ -70,29 +94,24 @@ jobs:
exit 1 exit 1
fi fi
# 5. Устанавливаем SSH-ключ для деплоя
- name: Install SSH key - name: Install SSH key
uses: webfactory/ssh-agent@v0.9.0 uses: webfactory/ssh-agent@v0.9.0
with: with:
ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }} ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }}
# 6. Деплой на сервер
- name: Deploy on server - name: Deploy on server
run: | run: |
ssh -o StrictHostKeyChecking=no ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << 'EOF' ssh -o StrictHostKeyChecking=no ${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }} << 'EOF'
# Переходим в папку с проектом (где лежит docker-compose.yml)
cd /opt/uvpn.shop/backend cd /opt/uvpn.shop/backend
# Логинимся в реестр Gitea (чтобы docker pull мог скачать образ)
echo "${{ secrets.ACCESS_TOKEN }}" | docker login git.uvpn.shop -u ${{ gitea.repository_owner }} --password-stdin echo "${{ secrets.ACCESS_TOKEN }}" | docker login git.uvpn.shop -u ${{ gitea.repository_owner }} --password-stdin
# Скачиваем свежий образ # Используем образ с правильным именем в нижнем регистре
export IMAGE_NAME="${{ steps.meta.outputs.image }}"
docker compose pull docker compose pull
docker compose down || true
# Перезапускаем контейнер с новым образом
docker compose down || true # Если контейнер не запущен — игнорируем ошибку
docker compose up -d docker compose up -d
# Очищаем неиспользуемые образы
docker image prune -f docker image prune -f
docker compose ps
EOF EOF
+24
View File
@@ -1,6 +1,13 @@
FROM ghcr.io/astral-sh/uv:python3.12-alpine AS builder FROM ghcr.io/astral-sh/uv:python3.12-alpine AS builder
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
WORKDIR /app WORKDIR /app
# Аргументы для метаданных
ARG BUILD_VERSION=unknown
ARG BUILD_DATE=unknown
ARG VCS_REF=unknown
ARG GITEA_REPOSITORY=unknown
RUN --mount=type=cache,target=/root/.cache/uv \ RUN --mount=type=cache,target=/root/.cache/uv \
--mount=type=bind,source=uv.lock,target=uv.lock \ --mount=type=bind,source=uv.lock,target=uv.lock \
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
@@ -8,7 +15,24 @@ RUN --mount=type=cache,target=/root/.cache/uv \
FROM python:3.12-alpine FROM python:3.12-alpine
WORKDIR /app WORKDIR /app
# Аргументы должны быть объявлены в каждом этапе
ARG BUILD_VERSION=unknown
ARG BUILD_DATE=unknown
ARG VCS_REF=unknown
ARG GITEA_REPOSITORY=unknown
# Метки для образа
LABEL org.opencontainers.image.title="UVPN Backend"
LABEL org.opencontainers.image.description="UVPN Shop Backend Service"
LABEL org.opencontainers.image.version="${BUILD_VERSION}"
LABEL org.opencontainers.image.created="${BUILD_DATE}"
LABEL org.opencontainers.image.revision="${VCS_REF}"
LABEL org.opencontainers.image.source="${GITEA_REPOSITORY}"
LABEL org.opencontainers.image.licenses="MIT"
COPY --from=builder /app/.venv /app/.venv COPY --from=builder /app/.venv /app/.venv
COPY . /app COPY . /app
ENV PATH="/app/.venv/bin:$PATH" ENV PATH="/app/.venv/bin:$PATH"
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"] CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
+1 -1
View File
@@ -3,7 +3,7 @@ services:
image: git.uvpn.shop/uvpn/backend:latest image: git.uvpn.shop/uvpn/backend:latest
container_name: uvpn-backend container_name: uvpn-backend
volumes: volumes:
- /opt/remnawave-minishop/data/tariffs.json:/app/data/tariffs.json - /opt/remnawave-minishop/data/:/app/data/
env_file: env_file:
- .env - .env
networks: networks:
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "uvpn-backend" name = "uvpn-backend"
version = "0.7.3" version = "0.7.7"
description = "FastAPI backend with Docker for uvpn.shop" description = "FastAPI backend with Docker for uvpn.shop"
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
+3 -1
View File
@@ -3,6 +3,7 @@ import logging
from os import getenv from os import getenv
from remnawave import RemnawaveSDK from remnawave import RemnawaveSDK
from remnawave.models.hosts import GetAllHostsResponseDto from remnawave.models.hosts import GetAllHostsResponseDto
from remnawave.models.nodes import GetOneNodeResponseDto
import re import re
@@ -40,7 +41,8 @@ async def get_nodes():
{ {
'name': clean_host_name(host.remark), 'name': clean_host_name(host.remark),
'address': host.address, 'address': host.address,
'port': host.port 'port': host.port,
'country_code': (await remnawave.nodes.get_one_node(uuid=host.nodes[0])).country_code
} }
for host in hosts.root if not host.is_disabled and not host.is_hidden for host in hosts.root if not host.is_disabled and not host.is_hidden