diff --git a/.gitea/workflows/build.yml b/.gitea/workflows/build.yml new file mode 100644 index 0000000..a4df63f --- /dev/null +++ b/.gitea/workflows/build.yml @@ -0,0 +1,59 @@ +name: Build and deploy + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Login to registry + run: echo "${{ secrets.TOKEN }}" | docker login ${{ secrets.GIT_HOST }} -u ${{ secrets.USERNAME }} --password-stdin + + - name: Build and push app + run: | + docker build -t ${{ secrets.GIT_HOST }}/${{ gitea.repository }}:app -f app/Dockerfile . + docker push ${{ secrets.GIT_HOST }}/${{ gitea.repository }}:app + + - name: Build and push migrations image + run: | + docker build -t ${{ secrets.GIT_HOST }}/${{ gitea.repository }}:migrations -f migrations/Dockerfile . + docker push ${{ secrets.GIT_HOST }}/${{ gitea.repository }}:migrations + + deploy: + runs-on: ubuntu-latest + needs: build + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install SSH key + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.DEPLOY_SSH_KEY }} + + - name: Add host to known_hosts + run: ssh-keyscan -H ${{ secrets.LXC_HOST }} >> ~/.ssh/known_hosts + + - name: Create remote deployment directory + run: ssh ${{ secrets.LXC_USER }}@${{ secrets.LXC_HOST }} "mkdir -p /srv/app" + + - name: Deploy docker-compose-ci.yml + run: scp docker-compose-ci.yml ${{ secrets.LXC_USER }}@${{ secrets.LXC_HOST }}:/srv/app/docker-compose.yml + + - name: Restart services + run: | + ssh ${{ secrets.LXC_USER }}@${{ secrets.LXC_HOST }} << 'EOF' + echo "${{ secrets.TOKEN }}" | docker login ${{ secrets.GIT_HOST }} -u ${{ secrets.USERNAME }} --password-stdin + docker pull ${{ secrets.GIT_HOST }}/${{ gitea.repository }}:app + docker pull ${{ secrets.GIT_HOST }}/${{ gitea.repository }}:migrations + cd /srv/app + docker compose up -d --force-recreate + docker image prune -f + EOF \ No newline at end of file diff --git a/app/main.py b/app/main.py index cbcaa7b..84c6c52 100644 --- a/app/main.py +++ b/app/main.py @@ -10,6 +10,8 @@ from app.api.routes import api_router from app.core.cache import init_cache, shutdown_cache from app.core.config import settings from app.core.middleware.cache_monitor import CacheAvailabilityMiddleware +from fastapi.middleware.cors import CORSMiddleware + def create_app() -> FastAPI: @@ -25,6 +27,13 @@ def create_app() -> FastAPI: application = FastAPI(title=settings.project_name, version=settings.version, lifespan=lifespan) application.include_router(api_router) application.add_middleware(CacheAvailabilityMiddleware) + application.add_middleware( + CORSMiddleware, + allow_origins=["https://kitchen-crm.k1nq.tech", "http://192.168.31.51"], + allow_credentials=True, + allow_methods=["*"], # Разрешить все HTTP-методы + allow_headers=["*"], # Разрешить все заголовки + ) return application diff --git a/docker-compose-ci.yml b/docker-compose-ci.yml new file mode 100644 index 0000000..fbd7412 --- /dev/null +++ b/docker-compose-ci.yml @@ -0,0 +1,91 @@ +services: + app: + image: ${GIT_HOST}/${GIT_USER}/${GIT_REPO}:app + restart: unless-stopped + command: uvicorn app.main:app --host 0.0.0.0 --port 8000 + env_file: + - .env + environment: + PROJECT_NAME: ${PROJECT_NAME} + VERSION: ${VERSION} + API_V1_PREFIX: ${API_V1_PREFIX} + DB_HOST: postgres + DB_PORT: ${DB_PORT} + DB_NAME: ${DB_NAME} + DB_USER: ${DB_USER} + DB_PASSWORD: ${DB_PASSWORD} + SQLALCHEMY_ECHO: ${SQLALCHEMY_ECHO} + JWT_SECRET_KEY: ${JWT_SECRET_KEY} + JWT_ALGORITHM: ${JWT_ALGORITHM} + ACCESS_TOKEN_EXPIRE_MINUTES: ${ACCESS_TOKEN_EXPIRE_MINUTES} + REFRESH_TOKEN_EXPIRE_DAYS: ${REFRESH_TOKEN_EXPIRE_DAYS} + REDIS_ENABLED: ${REDIS_ENABLED} + REDIS_URL: redis://redis:6379/0 + ANALYTICS_CACHE_TTL_SECONDS: ${ANALYTICS_CACHE_TTL_SECONDS} + ANALYTICS_CACHE_BACKOFF_MS: ${ANALYTICS_CACHE_BACKOFF_MS} + ports: + - "80:8000" + healthcheck: + test: ["CMD", "wget", "-qO-", "http://localhost:8000/health"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 10s + depends_on: + postgres: + condition: service_started + redis: + condition: service_started + migrations: + condition: service_completed_successfully + + migrations: + image: ${GIT_HOST}/${GIT_USER}/${GIT_REPO}:migrations + restart: "no" + env_file: + - .env + environment: + DB_HOST: postgres + REDIS_URL: redis://redis:6379/0 + depends_on: + postgres: + condition: service_started + + postgres: + image: postgres:16-alpine + environment: + POSTGRES_DB: ${DB_NAME} + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + ports: + - "5432:5432" + volumes: + - /mnt/data/postgres:/var/lib/postgresql/data + restart: unless-stopped + healthcheck: + test: + [ + "CMD", + "pg_isready", + "-U", + "${DB_USER}", + "-d", + "${DB_NAME}", + ] + interval: 30s + timeout: 5s + retries: 5 + start_period: 10s + + redis: + image: redis:7-alpine + command: redis-server --save "" --appendonly no + restart: unless-stopped + ports: + - "6379:6379" + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 30s + timeout: 5s + retries: 5 + start_period: 5s diff --git a/docker-compose.yml b/docker-compose-dev.yml similarity index 100% rename from docker-compose.yml rename to docker-compose-dev.yml diff --git a/migrations/Dockerfile b/migrations/Dockerfile new file mode 100644 index 0000000..fef17ae --- /dev/null +++ b/migrations/Dockerfile @@ -0,0 +1,27 @@ +# syntax=docker/dockerfile:1.7 + +FROM ghcr.io/astral-sh/uv:python3.14-alpine AS builder +WORKDIR /opt/app + +COPY pyproject.toml uv.lock ./ +RUN uv sync --frozen --no-dev + +COPY app ./app +COPY migrations ./migrations +COPY alembic.ini . + +FROM python:3.14-alpine AS runtime + +ENV PYTHONUNBUFFERED=1 PYTHONDONTWRITEBYTECODE=1 +ENV PATH="/opt/app/.venv/bin:${PATH}" + +WORKDIR /opt/app + +RUN apk add --no-cache libpq + +COPY --from=builder /opt/app/.venv /opt/app/.venv +COPY --from=builder /opt/app/app ./app +COPY --from=builder /opt/app/migrations ./migrations +COPY --from=builder /opt/app/alembic.ini . + +ENTRYPOINT ["alembic", "upgrade", "head"] \ No newline at end of file