From 6d7124d274cd7e9ca4e9cd450981195739a1b263 Mon Sep 17 00:00:00 2001 From: AlexBelyan Date: Tue, 19 May 2026 23:27:37 +0300 Subject: [PATCH] Fixed deploy pipeline --- .gitea/workflows/deploy.yaml | 41 +++++--- infra/{docker => }/application/Dockerfile | 21 ++--- infra/docker-compose.yml | 35 ++++--- infra/webhook/Dockerfile | 6 +- infra/webhook/go.mod | 4 +- infra/webhook/go.sum | 1 - infra/webhook/main.go | 110 +++++++++++++++------- 7 files changed, 140 insertions(+), 78 deletions(-) rename infra/{docker => }/application/Dockerfile (67%) diff --git a/.gitea/workflows/deploy.yaml b/.gitea/workflows/deploy.yaml index 202a898..695635a 100644 --- a/.gitea/workflows/deploy.yaml +++ b/.gitea/workflows/deploy.yaml @@ -11,7 +11,7 @@ jobs: steps: - name: Checkout code - uses: actions/checkout@v4 + uses: https://gitea.com/actions/checkout@v4 - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -25,32 +25,45 @@ jobs: - name: Build and push postgres image uses: docker/build-push-action@v5 - if: | - contains(github.event.commits[0].modified, 'infra/docker/postgres-pg-cron') || - contains(github.event.commits[0].added, 'infra/docker/postgres-pg-cron') +# if: | +# contains(github.event.commits[0].modified, 'infra/docker/postgres-pg-cron') || +# contains(github.event.commits[0].added, 'infra/docker/postgres-pg-cron') with: context: . file: infra/docker/postgres-pg-cron/Dockerfile push: true tags: | ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub-postgres:latest - cache-from: type=gha - cache-to: type=gha,mode=max + ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:${{ github.sha }} + cache-from: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache + cache-to: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache,mode=max + +# - name: Build and push webhook image +# uses: docker/build-push-action@v5 +# with: +# context: . +# file: infra/webhook/Dockerfile +# push: true +# tags: | +# ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub-webhook:latest +# ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:${{ github.sha }} +# cache-from: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache +# cache-to: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache,mode=max - name: Build and push app image uses: docker/build-push-action@v5 with: context: . - file: infra/docker/application/Dockerfile + file: infra/application/Dockerfile push: true tags: | ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:latest ${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:${{ github.sha }} - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache + cache-to: type=registry,ref=${{ secrets.REGISTRY_URL }}/${{ secrets.REGISTRY_USER }}/familyhub:cache,mode=max - - name: Trigger deploy - run: | - curl -s -X POST \ - -H "X-Webhook-Secret: ${{ secrets.WEBHOOK_SECRET }}" \ - "http://10.0.0.2:9001/deploy?container=familyhub" \ No newline at end of file +# - name: Trigger deploy +# run: | +# curl -s -X POST \ +# -H "X-Webhook-Secret: ${{ secrets.WEBHOOK_SECRET }}" \ +# "http://10.0.0.2:9001/deploy?container=familyhub" \ No newline at end of file diff --git a/infra/docker/application/Dockerfile b/infra/application/Dockerfile similarity index 67% rename from infra/docker/application/Dockerfile rename to infra/application/Dockerfile index 0f62880..be8a7df 100644 --- a/infra/docker/application/Dockerfile +++ b/infra/application/Dockerfile @@ -14,7 +14,7 @@ RUN npm run build # ================================ # Stage 2: сборка Go # ================================ -FROM golang:1.25-alpine AS backend +FROM golang:1.26-bookworm AS backend WORKDIR /app @@ -27,22 +27,19 @@ COPY backend/ ./ # встраиваем собранную статику Vue COPY --from=frontend /app/dist ./src/api/dist - +# Миграции кладём туда, откуда Go их ищет +COPY backend/migrations ./migrations # сборка бинарника RUN CGO_ENABLED=0 GOOS=linux go build -o server ./src/ # ================================ # Stage 3: финальный образ # ================================ -FROM alpine:3.19 +FROM scratch -# нужен для корректной работы TLS и временных зон -RUN apk add --no-cache ca-certificates tzdata +COPY --from=backend /app/server /server +COPY --from=backend /app/migrations /migrations +COPY --from=backend /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ +COPY --from=backend /usr/share/zoneinfo /usr/share/zoneinfo -WORKDIR /app - -COPY --from=backend /app/server ./server - -EXPOSE 8080 - -ENTRYPOINT ["./server"] \ No newline at end of file +ENTRYPOINT ["/server"] \ No newline at end of file diff --git a/infra/docker-compose.yml b/infra/docker-compose.yml index 4ec4f7d..57c030d 100644 --- a/infra/docker-compose.yml +++ b/infra/docker-compose.yml @@ -3,16 +3,23 @@ version: '3.9' services: app: image: git.myhomecloud.tech/admin/familyhub:latest - container_name: familyhub + container_name: application restart: unless-stopped ports: - - "10.0.0.2:8000:8000" # только через WireGuard + - "8000:8000" environment: - - DB_HOST=db - - DB_PORT=5432 - - DB_USER=familyUser - - DB_PASSWORD=familyPass - - DB_NAME=familyHubDB + - DB_HOST=${DB_HOST} + - DB_PORT=${DB_PORT} + - DB_USER=${DB_USER} + - DB_PASSWORD=${DB_PASSWORD} + - DB_NAME=${DB_NAME} + - BOT_TOKEN=${BOT_TOKEN} + - GOOGLE_APPLICATION_CREDENTIALS=${GOOGLE_APPLICATION_CREDENTIALS} + - RUN_MODE=${RUN_MODE} + - API_SECRET=${API_SECRET} + - DB_PATH=postgres://${DB_USER}:${DB_PASSWORD}@${DB_HOST}:${DB_PORT}/${DB_NAME}?sslmode=disable + - OPEN_API_ENABLED=${OPEN_API_ENABLED} + - DEBUG_MODE=${DEBUG_MODE} depends_on: - db networks: @@ -23,6 +30,8 @@ services: container_name: postgres restart: always pull_policy: always + ports: + - "5432:5432" command: - postgres - -c @@ -30,9 +39,9 @@ services: - -c - cron.database_name=familyHubDB environment: - POSTGRES_USER: familyUser - POSTGRES_PASSWORD: familyPass - POSTGRES_DB: familyHubDB + POSTGRES_USER: ${DB_USER} + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_NAME} volumes: - postgres-data:/var/lib/postgresql/data - ./init:/docker-entrypoint-initdb.d @@ -45,14 +54,14 @@ services: restart: unless-stopped pull_policy: always ports: - - "10.0.0.2:9001:9001" + - "9001:9001" environment: - WEBHOOK_SECRET=${WEBHOOK_SECRET} - - COMPOSE_FILE=/compose/docker-compose.yml + # - COMPOSE_FILE=/compose/docker-compose.yml - COMPOSE_PROJECT=familyhub volumes: - /var/run/docker.sock:/var/run/docker.sock - - ./docker-compose.yml:/compose/docker-compose.yml:ro + # - ./docker-compose.yml:/compose/docker-compose.yml:ro networks: - family-hub-net diff --git a/infra/webhook/Dockerfile b/infra/webhook/Dockerfile index fea7be5..dbcc560 100644 --- a/infra/webhook/Dockerfile +++ b/infra/webhook/Dockerfile @@ -1,12 +1,12 @@ -FROM golang:1.25-alpine AS builder +FROM golang:1.26-bookworm AS builder WORKDIR /app COPY infra/webhook/ . RUN go mod download && \ CGO_ENABLED=0 GOOS=linux go build -o webhook . -FROM alpine:3.19 -RUN apk add --no-cache docker-cli ca-certificates +FROM scratch COPY --from=builder /app/webhook /webhook +COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ EXPOSE 9001 ENTRYPOINT ["/webhook"] \ No newline at end of file diff --git a/infra/webhook/go.mod b/infra/webhook/go.mod index a3261a0..e4ca30e 100644 --- a/infra/webhook/go.mod +++ b/infra/webhook/go.mod @@ -1,5 +1,3 @@ module webhook -go 1.25 - -require github.com/docker/docker v26.1.0+incompatible \ No newline at end of file +go 1.25.0 diff --git a/infra/webhook/go.sum b/infra/webhook/go.sum index e358dc9..e69de29 100644 --- a/infra/webhook/go.sum +++ b/infra/webhook/go.sum @@ -1 +0,0 @@ -github.com/docker/docker v26.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= diff --git a/infra/webhook/main.go b/infra/webhook/main.go index 50c05a0..4734ddb 100644 --- a/infra/webhook/main.go +++ b/infra/webhook/main.go @@ -3,79 +3,125 @@ package main import ( "context" "encoding/json" + "fmt" + "io" "log" + "net" "net/http" + "net/url" "os" - "os/exec" - - "github.com/docker/docker/client" ) +func newDockerClient() *http.Client { + return &http.Client{ + Transport: &http.Transport{ + DialContext: func(ctx context.Context, _, _ string) (net.Conn, error) { + return net.Dial("unix", "/var/run/docker.sock") + }, + }, + } +} + +func getImageName(containerName string) (string, error) { + client := newDockerClient() + resp, err := client.Get("http://localhost/containers/" + containerName + "/json") + if err != nil { + return "", err + } + defer resp.Body.Close() + + var result struct { + Config struct { + Image string `json:"Image"` + } `json:"Config"` + } + if err := json.NewDecoder(resp.Body).Decode(&result); err != nil { + return "", err + } + return result.Config.Image, nil +} + +func pullImage(imageName string) error { + client := newDockerClient() + resp, err := client.Post( + "http://localhost/images/create?fromImage="+url.QueryEscape(imageName), + "application/json", + nil, + ) + if err != nil { + return err + } + defer resp.Body.Close() + // читаем до конца чтобы pull завершился полностью + io.Copy(io.Discard, resp.Body) + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("pull failed with status: %d", resp.StatusCode) + } + return nil +} + +func restartContainer(containerName string) error { + client := newDockerClient() + resp, err := client.Post( + "http://localhost/containers/"+containerName+"/restart", + "application/json", + nil, + ) + if err != nil { + return err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusNoContent { + return fmt.Errorf("restart failed with status: %d", resp.StatusCode) + } + return nil +} + func main() { secret := os.Getenv("WEBHOOK_SECRET") - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) - if err != nil { - log.Fatal("failed to create docker client:", err) - } - defer cli.Close() - http.HandleFunc("/deploy", func(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } - if r.Header.Get("X-Webhook-Secret") != secret { http.Error(w, "unauthorized", http.StatusUnauthorized) return } - // имя контейнера берём из query параметра containerName := r.URL.Query().Get("container") if containerName == "" { http.Error(w, "container parameter is required", http.StatusBadRequest) return } - ctx := context.Background() - - // получаем инфо о контейнере чтобы узнать имя образа - inspect, err := cli.ContainerInspect(ctx, containerName) + imageName, err := getImageName(containerName) if err != nil { log.Printf("failed to inspect container %s: %v", containerName, err) http.Error(w, "container not found", http.StatusNotFound) return } - - imageName := inspect.Config.Image log.Printf("Container: %s, Image: %s", containerName, imageName) - // тянем новый образ - log.Println("Pulling new image...") - pull := exec.Command("docker", "pull", imageName) - pull.Stdout = os.Stdout - pull.Stderr = os.Stderr - if err := pull.Run(); err != nil { - log.Println("pull failed:", err) + log.Printf("Pulling image %s...", imageName) + if err := pullImage(imageName); err != nil { + log.Printf("pull failed: %v", err) http.Error(w, "pull failed", http.StatusInternalServerError) return } - // перезапускаем контейнер log.Printf("Restarting container %s...", containerName) - restart := exec.Command("docker", "restart", containerName) - restart.Stdout = os.Stdout - restart.Stderr = os.Stderr - if err := restart.Run(); err != nil { - log.Println("restart failed:", err) + if err := restartContainer(containerName); err != nil { + log.Printf("restart failed: %v", err) http.Error(w, "restart failed", http.StatusInternalServerError) return } log.Printf("Deploy of %s completed", containerName) - w.WriteHeader(http.StatusOK) - + w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(map[string]string{ "status": "ok", "container": containerName,