Added possibility deploy with k3s #11

Merged
admin merged 1 commits from 8-Add-Pipelines-for-autodeploy into main 2026-05-26 23:03:45 +03:00
33 changed files with 283 additions and 195 deletions
Showing only changes of commit 39425af43e - Show all commits
+16 -4
View File
@@ -49,8 +49,20 @@ jobs:
cache-from: type=gha
cache-to: type=gha,mode=max
- name: Trigger deploy
- name: Install kubectl
run: |
curl -s -X POST \
-H "X-Webhook-Secret: ${{ secrets.WEBHOOK_SECRET }}" \
"http://10.0.0.2:9001/deploy?container=familyhub"
curl -LO "https://dl.k8s.io/release/$(curl -L -s https://dl.k8s.io/release/stable.txt)/bin/linux/amd64/kubectl"
chmod +x kubectl
sudo mv kubectl /usr/local/bin/
- name: Deploy to k3s
env:
KUBECONFIG_DATA: ${{ secrets.KUBECONFIG }}
run: |
mkdir -p ~/.kube
echo "$KUBECONFIG_DATA" > ~/.kube/config
chmod 600 ~/.kube/config
kubectl rollout restart deployment/application -n family-hub
kubectl rollout restart deployment/postgres -n family-hub
kubectl rollout status deployment/application -n family-hub --timeout=120s
kubectl rollout status deployment/postgres -n family-hub --timeout=120s
+2
View File
@@ -7,3 +7,5 @@ archive
volumes
*.dtmp
*.gocache
infra/k8s/secrets.yaml
infra/k8s/google-creds.yaml
@@ -0,0 +1 @@
DROP EXTENSION pg_cron;
+29 -4
View File
@@ -2,6 +2,7 @@ package config
import (
"errors"
"fmt"
"os"
"strings"
@@ -33,7 +34,6 @@ func Load() (Config, error) {
mode := os.Getenv("RUN_MODE")
debugMode := os.Getenv("DEBUG_MODE") == "true"
botToken := os.Getenv("BOT_TOKEN")
dbConnectionString := os.Getenv("DB_PATH")
ocrTokenPath := os.Getenv("GOOGLE_APPLICATION_CREDENTIALS")
apiPort := os.Getenv("API_PORT")
apiHost := os.Getenv("API_HOST")
@@ -42,6 +42,7 @@ func Load() (Config, error) {
openAPIEndpoint := os.Getenv("OPEN_API_ENDPOINT")
runMode, err := ParseRunMode(mode)
dbConnectionString := buildConnectionString()
if err != nil {
warnings = append(warnings, err.Error())
}
@@ -61,9 +62,6 @@ func Load() (Config, error) {
if apiSecret == "" {
warnings = append(warnings, "Missing required environment variable: API_SECRET")
}
if dbConnectionString == "" {
dbConnectionString = "sqlite://data/app.db"
}
if apiHost == "" {
apiHost = "localhost"
}
@@ -92,3 +90,30 @@ func Load() (Config, error) {
TelegramApi: "https://api.telegram.org",
}, nil
}
func buildConnectionString() string {
// если задана готовая строка — используем её (удобно для локальной разработки через .env)
if dsn := os.Getenv("DB_PATH"); dsn != "" {
return dsn
}
// собираем из отдельных переменных (для Kubernetes)
host := os.Getenv("DB_HOST")
port := os.Getenv("DB_PORT")
user := os.Getenv("DB_USER")
password := os.Getenv("DB_PASSWORD")
dbName := os.Getenv("DB_NAME")
if host == "" || user == "" || password == "" || dbName == "" {
return ""
}
if port == "" {
port = "5432"
}
return fmt.Sprintf(
"postgres://%s:%s@%s:%s/%s?sslmode=disable",
user, password, host, port, dbName,
)
}
@@ -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"]
ENTRYPOINT ["/server"]
-63
View File
@@ -1,63 +0,0 @@
version: '3.9'
services:
app:
image: git.myhomecloud.tech/admin/familyhub:latest
container_name: familyhub
restart: unless-stopped
ports:
- "10.0.0.2:8000:8000" # только через WireGuard
environment:
- DB_HOST=db
- DB_PORT=5432
- DB_USER=familyUser
- DB_PASSWORD=familyPass
- DB_NAME=familyHubDB
depends_on:
- db
networks:
- family-hub-net
db:
image: git.myhomecloud.tech/admin/familyhub-postgres:latest
container_name: postgres
restart: always
pull_policy: always
command:
- postgres
- -c
- shared_preload_libraries=pg_cron
- -c
- cron.database_name=familyHubDB
environment:
POSTGRES_USER: familyUser
POSTGRES_PASSWORD: familyPass
POSTGRES_DB: familyHubDB
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d
networks:
- family-hub-net
webhook:
image: git.myhomecloud.tech/admin/familyhub-webhook:latest
container_name: webhook
restart: unless-stopped
pull_policy: always
ports:
- "10.0.0.2:9001:9001"
environment:
- WEBHOOK_SECRET=${WEBHOOK_SECRET}
- 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
networks:
- family-hub-net
networks:
family-hub-net:
volumes:
postgres-data:
+55
View File
@@ -0,0 +1,55 @@
version: '3.9'
services:
app:
image: git.myhomecloud.tech/admin/familyhub:latest
container_name: application
restart: unless-stopped
ports:
- "8000:8000"
environment:
- 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:
- family-hub-net
db:
image: git.myhomecloud.tech/admin/familyhub-postgres:latest
container_name: postgres
restart: always
pull_policy: always
ports:
- "5432:5432"
command:
- postgres
- -c
- shared_preload_libraries=pg_cron
- -c
- cron.database_name=familyHubDB
environment:
POSTGRES_USER: ${DB_USER}
POSTGRES_PASSWORD: ${DB_PASSWORD}
POSTGRES_DB: ${DB_NAME}
volumes:
- postgres-data:/var/lib/postgresql/data
- ./init:/docker-entrypoint-initdb.d
networks:
- family-hub-net
networks:
family-hub-net:
volumes:
postgres-data:
-5
View File
@@ -1,5 +0,0 @@
FROM postgres:16
RUN apt-get update \
&& apt-get install -y --no-install-recommends postgresql-16-cron \
&& rm -rf /var/lib/apt/lists/*
+62
View File
@@ -0,0 +1,62 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: application
namespace: family-hub
spec:
replicas: 1
selector:
matchLabels:
app: application
template:
metadata:
labels:
app: application
spec:
containers:
- name: application
image: git.myhomecloud.tech/admin/familyhub:latest
ports:
- containerPort: 8000
envFrom:
- configMapRef:
name: family-hub-config
- secretRef:
name: family-hub-secrets
env:
- name: GOOGLE_APPLICATION_CREDENTIALS
value: /secrets/credentials.json
volumeMounts:
- name: google-credentials
mountPath: /secrets
readOnly: true
# livenessProbe:
# httpGet:
# path: /api/v1/health
# port: 8000
# initialDelaySeconds: 10
# periodSeconds: 30
# readinessProbe:
# httpGet:
# path: /api/v1/health
# port: 8000
# initialDelaySeconds: 5
# periodSeconds: 10
volumes:
- name: google-credentials
secret:
secretName: google-credentials
imagePullSecrets:
- name: gitea-registry
---
apiVersion: v1
kind: Service
metadata:
name: application
namespace: family-hub
spec:
selector:
app: application
ports:
- port: 9876
targetPort: 8000
+15
View File
@@ -0,0 +1,15 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: family-hub-config
namespace: family-hub
data:
DB_HOST: postgres
DB_PORT: "5432"
DB_NAME: familyHubDB
DB_USER: familyUser
API_PORT: "8000"
API_HOST: 0.0.0.0
RUN_MODE: standalone
OPEN_API_ENABLED: "true"
DEBUG_MODE: "false"
+19
View File
@@ -0,0 +1,19 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: application
namespace: family-hub
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: web
spec:
rules:
- host: application.local
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: application
port:
number: 9876
+4
View File
@@ -0,0 +1,4 @@
apiVersion: v1
kind: Namespace
metadata:
name: family-hub
+62
View File
@@ -0,0 +1,62 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: postgres
namespace: family-hub
spec:
replicas: 1
selector:
matchLabels:
app: postgres
template:
metadata:
labels:
app: postgres
spec:
containers:
- name: postgres
image: git.myhomecloud.tech/admin/familyhub-postgres:latest
env:
- name: POSTGRES_USER
value: familyUser
- name: POSTGRES_PASSWORD
valueFrom:
secretKeyRef:
name: family-hub-secrets
key: DB_PASSWORD
- name: POSTGRES_DB
value: familyHubDB
ports:
- containerPort: 5432
volumeMounts:
- name: postgres-data
mountPath: /var/lib/postgresql/data
imagePullSecrets:
- name: gitea-registry
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pvc
---
apiVersion: v1
kind: Service
metadata:
name: postgres
namespace: family-hub
spec:
selector:
app: postgres
ports:
- port: 5432
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pvc
namespace: family-hub
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
+8
View File
@@ -0,0 +1,8 @@
FROM postgres:16
RUN apt-get update \
&& apt-get install -y --no-install-recommends postgresql-16-cron \
&& rm -rf /var/lib/apt/lists/* \
RUN echo "shared_preload_libraries = 'pg_cron'" >> /usr/share/postgresql/postgresql.conf.sample \
&& echo "cron.database_name = 'familyHubDB'" >> /usr/share/postgresql/postgresql.conf.sample
-12
View File
@@ -1,12 +0,0 @@
FROM golang:1.25-alpine 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
COPY --from=builder /app/webhook /webhook
EXPOSE 9001
ENTRYPOINT ["/webhook"]
-5
View File
@@ -1,5 +0,0 @@
module webhook
go 1.25
require github.com/docker/docker v26.1.0+incompatible
-1
View File
@@ -1 +0,0 @@
github.com/docker/docker v26.1.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk=
-88
View File
@@ -1,88 +0,0 @@
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"os"
"os/exec"
"github.com/docker/docker/client"
)
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)
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)
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)
http.Error(w, "restart failed", http.StatusInternalServerError)
return
}
log.Printf("Deploy of %s completed", containerName)
w.WriteHeader(http.StatusOK)
json.NewEncoder(w).Encode(map[string]string{
"status": "ok",
"container": containerName,
"image": imageName,
})
})
log.Println("Webhook server listening on :9001")
log.Fatal(http.ListenAndServe(":9001", nil))
}