commit 1b340362be3256ad7af4f5d9fcb499e1c3649906 Author: Andrey Epifancev Date: Mon Aug 11 19:26:57 2025 +0400 Initial commit: Go webhook server for Quartz site rebuild diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e6ed380 --- /dev/null +++ b/.gitignore @@ -0,0 +1,48 @@ +# Binaries for programs and plugins +*.exe +*.exe~ +*.dll +*.so +*.dylib + +# Test binary, built with `go test -c` +*.test + +# Output of the go coverage tool, specifically when used with LiteIDE +*.out + +# Dependency directories (remove the comment below to include it) +# vendor/ + +# Go workspace file +go.work + +# Air temporary files +tmp/ +.air.toml + +# Build artifacts +go-webhook-server +main + +# Logs +*.log +build-errors.log + +# IDE files +.vscode/ +.idea/ +*.swp +*.swo + +# OS generated files +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db + +# Docker +.dockerignore diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6d8cc90 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,51 @@ +# Многоэтапная сборка для Go приложения +FROM golang:1.21-alpine AS builder + +# Устанавливаем необходимые пакеты для сборки +RUN apk add --no-cache git ca-certificates tzdata + +# Устанавливаем рабочую директорию +WORKDIR /app + +# Копируем go mod файлы +COPY go.mod go.sum ./ + +# Скачиваем зависимости +RUN go mod download + +# Копируем исходный код +COPY . . + +# Собираем приложение +RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o main . + +# Финальный образ +FROM alpine:latest + +# Устанавливаем необходимые пакеты для работы +RUN apk --no-cache add ca-certificates git tzdata + +# Создаем пользователя для безопасности +RUN addgroup -g 1001 -S appgroup && \ + adduser -u 1001 -S appuser -G appgroup + +# Устанавливаем рабочую директорию +WORKDIR /app + +# Копируем собранное приложение из builder этапа +COPY --from=builder /app/main . + +# Копируем конфигурационные файлы если есть +COPY --from=builder /app/config.* ./ + +# Меняем владельца файлов +RUN chown -R appuser:appgroup /app + +# Переключаемся на непривилегированного пользователя +USER appuser + +# Открываем порт +EXPOSE 3000 + +# Запускаем приложение +CMD ["./main"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..d72945b --- /dev/null +++ b/Makefile @@ -0,0 +1,102 @@ +.PHONY: help build run test clean docker-build docker-run docker-stop install-deps + +# Переменные +BINARY_NAME=go-webhook-server +DOCKER_IMAGE=go-webhook-server +DOCKER_CONTAINER=go-webhook-server + +# Помощь +help: ## Показать справку по командам + @echo "Доступные команды:" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +# Установка зависимостей +install-deps: ## Установить Go зависимости + go mod download + go mod tidy + +# Сборка +build: install-deps ## Собрать бинарный файл + @echo "Сборка $(BINARY_NAME)..." + go build -o $(BINARY_NAME) main.go + @echo "Сборка завершена: $(BINARY_NAME)" + +# Запуск +run: build ## Запустить сервис локально + @echo "Запуск $(BINARY_NAME)..." + ./$(BINARY_NAME) + +# Тестирование +test: install-deps ## Запустить тесты + @echo "Запуск тестов..." + go test -v ./... + +# Очистка +clean: ## Очистить собранные файлы + @echo "Очистка..." + rm -f $(BINARY_NAME) + @echo "Очистка завершена" + +# Docker команды +docker-build: ## Собрать Docker образ + @echo "Сборка Docker образа..." + docker build -t $(DOCKER_IMAGE) . + @echo "Docker образ собран: $(DOCKER_IMAGE)" + +docker-run: docker-build ## Запустить Docker контейнер + @echo "Запуск Docker контейнера..." + docker run -d \ + --name $(DOCKER_CONTAINER) \ + -p 3000:3000 \ + -v obsidian_repo:/obsidian:ro \ + -v quartz_repo:/quartz:ro \ + -v public_site:/public \ + $(DOCKER_IMAGE) + @echo "Docker контейнер запущен: $(DOCKER_CONTAINER)" + +docker-stop: ## Остановить Docker контейнер + @echo "Остановка Docker контейнера..." + docker stop $(DOCKER_CONTAINER) || true + docker rm $(DOCKER_CONTAINER) || true + @echo "Docker контейнер остановлен" + +docker-logs: ## Показать логи Docker контейнера + docker logs -f $(DOCKER_CONTAINER) + +# Docker Compose команды +compose-up: ## Запустить сервис через Docker Compose + @echo "Запуск через Docker Compose..." + docker-compose up -d + @echo "Сервис запущен" + +compose-down: ## Остановить сервис через Docker Compose + @echo "Остановка через Docker Compose..." + docker-compose down + @echo "Сервис остановлен" + +compose-logs: ## Показать логи Docker Compose + docker-compose logs -f + +# Проверка состояния +status: ## Показать статус сервиса + @echo "Проверка статуса сервиса..." + @curl -s http://localhost:3000/health || echo "Сервис недоступен" + +# Webhook тест +test-webhook: ## Отправить тестовый webhook + @echo "Отправка тестового webhook..." + @curl -X POST http://localhost:3000/webhook + @echo "" + +# Полная пересборка +rebuild: clean build ## Полная пересборка проекта + +# Разработка +dev: ## Запуск в режиме разработки с автоперезагрузкой + @echo "Запуск в режиме разработки..." + @if command -v air > /dev/null; then \ + air; \ + else \ + echo "Air не установлен. Установите: go install github.com/cosmtrek/air@latest"; \ + go run main.go; \ + fi diff --git a/README.md b/README.md new file mode 100644 index 0000000..41d5bcc --- /dev/null +++ b/README.md @@ -0,0 +1,152 @@ +# Go Webhook Server + +Go-версия webhook сервиса для автоматической пересборки Quartz сайта при получении webhook'а. + +## Особенности + +- **Написан на Go** - быстрый и эффективный +- **Использует Gin** - легковесный веб-фреймворк +- **Интеграция с Git** - автоматическое обновление репозитория +- **Асинхронная обработка** - webhook'и обрабатываются в фоне +- **Graceful shutdown** - корректное завершение работы +- **Структурированное логирование** - с использованием logrus +- **Конфигурируемость** - через переменные окружения + +## Архитектура + +Сервис состоит из следующих компонентов: + +1. **HTTP сервер** на Gin с эндпоинтами: + - `POST /webhook` - основной webhook для пересборки + - `GET /health` - проверка состояния сервиса + +2. **Git интеграция** - автоматическое обновление репозитория Obsidian +3. **Quartz сборка** - запуск процесса сборки статического сайта +4. **Файловые операции** - копирование собранных файлов в публичную директорию + +## Конфигурация + +Сервис настраивается через переменные окружения: + +| Переменная | Описание | По умолчанию | +|------------|----------|--------------| +| `PORT` | Порт для HTTP сервера | `3000` | +| `OBSIDIAN_PATH` | Путь к репозиторию Obsidian | `/obsidian` | +| `QUARTZ_PATH` | Путь к директории Quartz | `/quartz` | +| `PUBLIC_PATH` | Путь к публичной директории | `/public` | +| `GIT_BRANCH` | Ветка Git для обновления | `main` | +| `GIT_REMOTE` | Имя удаленного репозитория | `origin` | + +## Запуск + +### Локально + +1. Установите Go 1.21+ +2. Скачайте зависимости: + ```bash + go mod download + ``` +3. Запустите сервис: + ```bash + go run main.go + ``` + +### В Docker + +1. Соберите образ: + ```bash + docker build -t go-webhook-server . + ``` + +2. Запустите контейнер: + ```bash + docker run -d \ + --name go-webhook-server \ + -p 3000:3000 \ + -v obsidian_repo:/obsidian:ro \ + -v quartz_repo:/quartz:ro \ + -v public_site:/public \ + go-webhook-server + ``` + +### С Docker Compose + +1. Запустите сервис: + ```bash + docker-compose up -d + ``` + +## Использование + +### Отправка webhook'а + +```bash +curl -X POST http://localhost:3000/webhook +``` + +### Проверка состояния + +```bash +curl http://localhost:3000/health +``` + +## Процесс сборки + +При получении webhook'а сервис выполняет: + +1. **Проверка репозитория** - убеждается что Git репозиторий существует +2. **Обновление кода** - выполняет `git pull` для получения последних изменений +3. **Установка зависимостей** - запускает `npm install` если необходимо +4. **Сборка сайта** - запускает `npm run quartz build` с указанием директории Obsidian +5. **Копирование файлов** - копирует собранные файлы в публичную директорию + +## Логирование + +Сервис использует структурированное логирование с помощью logrus: + +- **Info** - информационные сообщения о процессе сборки +- **Error** - ошибки, возникающие в процессе +- **HTTP логи** - все HTTP запросы с деталями + +## Безопасность + +- Контейнер запускается от непривилегированного пользователя +- Используется Alpine Linux для минимального attack surface +- Graceful shutdown для корректного завершения работы + +## Мониторинг + +Сервис предоставляет health check эндпоинт для мониторинга: + +```json +{ + "status": "ok", + "timestamp": "2025-01-27T10:30:00Z", + "service": "go-webhook-server", + "version": "1.0.0" +} +``` + +## Разработка + +### Структура проекта + +``` +go-webhook-server/ +├── main.go # Основной файл приложения +├── go.mod # Go модуль +├── go.sum # Хеши зависимостей +├── Dockerfile # Docker образ +├── docker-compose.yml # Docker Compose конфигурация +└── README.md # Документация +``` + +### Зависимости + +- `github.com/gin-gonic/gin` - веб-фреймворк +- `github.com/go-git/go-git/v5` - Git клиент +- `github.com/sirupsen/logrus` - логирование + +## Лицензия + +MIT diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ed155cd --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,36 @@ +version: '3.8' + +services: + go-webhook-server: + build: . + container_name: go-webhook-server + ports: + - "3000:3000" + environment: + - PORT=3000 + - OBSIDIAN_PATH=/obsidian + - QUARTZ_PATH=/quartz + - PUBLIC_PATH=/public + - GIT_BRANCH=main + - GIT_REMOTE=origin + volumes: + - obsidian_repo:/obsidian:ro + - quartz_repo:/quartz:ro + - public_site:/public + restart: unless-stopped + networks: + - webhook-network + +volumes: + obsidian_repo: + external: true + name: obsidian_repo + quartz_repo: + external: true + name: quartz_repo + public_site: + name: public_site + +networks: + webhook-network: + driver: bridge diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..58ce12f --- /dev/null +++ b/go.mod @@ -0,0 +1,55 @@ +module go-webhook-server + +go 1.20 + +require ( + github.com/gin-gonic/gin v1.9.1 + github.com/go-git/go-git/v5 v5.10.0 + github.com/sirupsen/logrus v1.9.3 +) + +require ( + dario.cat/mergo v1.0.0 // indirect + github.com/Microsoft/go-winio v0.6.1 // indirect + github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect + github.com/acomagu/bufpipe v1.0.4 // indirect + github.com/bytedance/sonic v1.9.1 // indirect + github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect + github.com/cloudflare/circl v1.3.3 // indirect + github.com/cyphar/filepath-securejoin v0.2.4 // indirect + github.com/emirpasic/gods v1.18.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.2 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect + github.com/go-git/go-billy/v5 v5.5.0 // indirect + github.com/go-playground/locales v0.14.1 // indirect + github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/validator/v10 v10.14.0 // indirect + github.com/goccy/go-json v0.10.2 // indirect + github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect + github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/kevinburke/ssh_config v1.2.0 // indirect + github.com/klauspost/cpuid/v2 v2.2.4 // indirect + github.com/leodido/go-urn v1.2.4 // indirect + github.com/mattn/go-isatty v0.0.19 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.8 // indirect + github.com/pjbgf/sha1cd v0.3.0 // indirect + github.com/sergi/go-diff v1.1.0 // indirect + github.com/skeema/knownhosts v1.2.0 // indirect + github.com/twitchyliquid64/golang-asm v0.15.1 // indirect + github.com/ugorji/go/codec v1.2.11 // indirect + github.com/xanzy/ssh-agent v0.3.3 // indirect + golang.org/x/arch v0.3.0 // indirect + golang.org/x/crypto v0.14.0 // indirect + golang.org/x/mod v0.12.0 // indirect + golang.org/x/net v0.17.0 // indirect + golang.org/x/sys v0.13.0 // indirect + golang.org/x/text v0.13.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/protobuf v1.30.0 // indirect + gopkg.in/warnings.v0 v0.1.2 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..bac0d78 --- /dev/null +++ b/go.sum @@ -0,0 +1,199 @@ +dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk= +dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk= +github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY= +github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow= +github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg= +github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0= +github.com/acomagu/bufpipe v1.0.4 h1:e3H4WUzM3npvo5uv95QuJM3cQspFNtFBzvJ2oNjKIDQ= +github.com/acomagu/bufpipe v1.0.4/go.mod h1:mxdxdup/WdsKVreO5GpW4+M/1CE2sMG4jeGJ2sYmHc4= +github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8= +github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= +github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0= +github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= +github.com/bytedance/sonic v1.9.1 h1:6iJ6NqdoxCDr6mbY8h18oSO+cShGSMRGCEo7F2h0x8s= +github.com/bytedance/sonic v1.9.1/go.mod h1:i736AoUSYt75HyZLoJW9ERYxcy6eaN6h4BZXU064P/U= +github.com/chenzhuoyu/base64x v0.0.0-20211019084208-fb5309c8db06/go.mod h1:DH46F32mSOjUmXrMHnKwZdA8wcEefY7UVqBKYGjpdQY= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 h1:qSGYFH7+jGhDF8vLC+iwCD4WpbV1EBDSzWkJODFLams= +github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311/go.mod h1:b583jCggY9gE99b6G5LEC39OIiVsWj+R97kbl5odCEk= +github.com/cloudflare/circl v1.3.3 h1:fE/Qz0QdIGqeWfnwq0RE0R7MI51s0M2E4Ga9kq5AEMs= +github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA= +github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= +github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU= +github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc= +github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ= +github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU= +github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= +github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= +github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI= +github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic= +github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU= +github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow= +github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4= +github.com/go-git/go-git/v5 v5.10.0 h1:F0x3xXrAWmhwtzoCokU4IMPcBdncG+HAAqi9FcOOjbQ= +github.com/go-git/go-git/v5 v5.10.0/go.mod h1:1FOZ/pQnqw24ghP2n7cunVl0ON55BsjPYvhWHvZGhoo= +github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= +github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= +github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= +github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= +github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= +github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg/+t63MyGU2n5js= +github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU= +github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU= +github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE= +github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A= +github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4= +github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk= +github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q= +github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4= +github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A= +github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA= +github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= +github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ= +github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4= +github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4= +github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= +github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= +github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= +github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/skeema/knownhosts v1.2.0 h1:h9r9cf0+u7wSE+M183ZtMGgOJKiL96brpaz5ekfJCpM= +github.com/skeema/knownhosts v1.2.0/go.mod h1:g4fPeYpque7P0xefxtGzV81ihjC8sX2IqpAoNkjxbMo= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= +github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +github.com/stretchr/testify v1.8.3 h1:RP3t2pwF7cMEbC1dqtB6poj3niw/9gnV4Cjg5oW5gtY= +github.com/stretchr/testify v1.8.3/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS4MhqMhdFk5YI= +github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08= +github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU= +github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= +github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM= +github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/arch v0.3.0 h1:02VY4/ZcO/gBOH6PUaoiptASxtXU10jazRCP865E97k= +golang.org/x/arch v0.3.0/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= +golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= +golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc= +golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= +golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= +golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= +golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= +golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= +golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng= +google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go new file mode 100644 index 0000000..a850ca0 --- /dev/null +++ b/main.go @@ -0,0 +1,292 @@ +package main + +import ( + "context" + "fmt" + "net/http" + "os" + "os/exec" + "path/filepath" + "time" + + "github.com/gin-gonic/gin" + "github.com/go-git/go-git/v5" + "github.com/sirupsen/logrus" +) + +type Config struct { + Port string + ObsidianPath string + QuartzPath string + PublicPath string + GitBranch string + GitRemote string +} + +type BuildResult struct { + Success bool `json:"success"` + Message string `json:"message"` + Error string `json:"error,omitempty"` +} + +var ( + config Config + logger *logrus.Logger +) + +func init() { + // Инициализация логгера + logger = logrus.New() + logger.SetFormatter(&logrus.TextFormatter{ + FullTimestamp: true, + }) + logger.SetLevel(logrus.InfoLevel) + + // Загрузка конфигурации + config = Config{ + Port: getEnv("PORT", "3000"), + ObsidianPath: getEnv("OBSIDIAN_PATH", "/obsidian"), + QuartzPath: getEnv("QUARTZ_PATH", "/quartz"), + PublicPath: getEnv("PUBLIC_PATH", "/public"), + GitBranch: getEnv("GIT_BRANCH", "main"), + GitRemote: getEnv("GIT_REMOTE", "origin"), + } + + logger.Infof("Configuration loaded: Port=%s, ObsidianPath=%s, QuartzPath=%s, PublicPath=%s, GitBranch=%s, GitRemote=%s", + config.Port, config.ObsidianPath, config.QuartzPath, config.PublicPath, config.GitBranch, config.GitRemote) +} + +func getEnv(key, defaultValue string) string { + if value := os.Getenv(key); value != "" { + return value + } + return defaultValue +} + +func buildSite() BuildResult { + logger.Info("Starting site build process...") + + // Проверяем существование репозитория + if _, err := os.Stat(filepath.Join(config.ObsidianPath, ".git")); os.IsNotExist(err) { + logger.Error("Repository not found") + return BuildResult{ + Success: false, + Message: "Repository not found", + Error: "Git repository does not exist at specified path", + } + } + + // Обновляем репозиторий + if err := updateRepository(); err != nil { + logger.Errorf("Failed to update repository: %v", err) + return BuildResult{ + Success: false, + Message: "Failed to update repository", + Error: err.Error(), + } + } + + // Собираем сайт с помощью Quartz + if err := buildQuartzSite(); err != nil { + logger.Errorf("Failed to build Quartz site: %v", err) + return BuildResult{ + Success: false, + Message: "Failed to build Quartz site", + Error: err.Error(), + } + } + + // Копируем собранные файлы в публичную директорию + if err := copyBuiltSite(); err != nil { + logger.Errorf("Failed to copy built site: %v", err) + return BuildResult{ + Success: false, + Message: "Failed to copy built site", + Error: err.Error(), + } + } + + logger.Info("Site built successfully!") + return BuildResult{ + Success: true, + Message: "Site built successfully", + } +} + +func updateRepository() error { + logger.Info("Updating repository...") + + // Открываем репозиторий + repo, err := git.PlainOpen(config.ObsidianPath) + if err != nil { + return fmt.Errorf("failed to open repository: %w", err) + } + + // Получаем worktree + worktree, err := repo.Worktree() + if err != nil { + return fmt.Errorf("failed to get worktree: %w", err) + } + + // Выполняем git pull + err = worktree.Pull(&git.PullOptions{ + RemoteName: config.GitRemote, + }) + if err != nil && err != git.NoErrAlreadyUpToDate { + return fmt.Errorf("failed to pull from remote: %w", err) + } + + if err == git.NoErrAlreadyUpToDate { + logger.Info("Repository is already up to date") + } else { + logger.Info("Repository updated successfully") + } + + return nil +} + +func buildQuartzSite() error { + logger.Info("Building site with Quartz...") + + // Проверяем существование package.json в директории Quartz + packageJSONPath := filepath.Join(config.QuartzPath, "package.json") + if _, err := os.Stat(packageJSONPath); os.IsNotExist(err) { + return fmt.Errorf("package.json not found in Quartz directory") + } + + // Выполняем npm install если node_modules не существует + nodeModulesPath := filepath.Join(config.QuartzPath, "node_modules") + if _, err := os.Stat(nodeModulesPath); os.IsNotExist(err) { + logger.Info("Installing npm dependencies...") + cmd := exec.Command("npm", "install") + cmd.Dir = config.QuartzPath + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to install npm dependencies: %w", err) + } + } + + // Выполняем сборку Quartz + cmd := exec.Command("npm", "run", "quartz", "build", "--", "-d", config.ObsidianPath) + cmd.Dir = config.QuartzPath + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to build Quartz site: %w", err) + } + + logger.Info("Quartz build completed successfully") + return nil +} + +func copyBuiltSite() error { + logger.Info("Copying built site to public directory...") + + // Очищаем публичную директорию + if err := os.RemoveAll(config.PublicPath); err != nil { + return fmt.Errorf("failed to clear public directory: %w", err) + } + + // Создаем публичную директорию заново + if err := os.MkdirAll(config.PublicPath, 0755); err != nil { + return fmt.Errorf("failed to create public directory: %w", err) + } + + // Копируем собранные файлы + quartzPublicPath := filepath.Join(config.QuartzPath, "public") + if _, err := os.Stat(quartzPublicPath); os.IsNotExist(err) { + return fmt.Errorf("Quartz public directory not found") + } + + // Используем cp команду для копирования + cmd := exec.Command("cp", "-r", quartzPublicPath+"/.", config.PublicPath+"/") + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to copy built site: %w", err) + } + + logger.Info("Site files copied successfully") + return nil +} + +func webhookHandler(c *gin.Context) { + logger.Info("Webhook received, starting site rebuild...") + + // Запускаем сборку в горутине для асинхронной обработки + go func() { + result := buildSite() + if result.Success { + logger.Info("Webhook build completed successfully") + } else { + logger.Errorf("Webhook build failed: %s", result.Error) + } + }() + + // Сразу возвращаем ответ + c.JSON(http.StatusAccepted, gin.H{ + "status": "accepted", + "message": "Build process started", + }) +} + +func healthHandler(c *gin.Context) { + c.JSON(http.StatusOK, gin.H{ + "status": "ok", + "timestamp": time.Now().UTC().Format(time.RFC3339), + "service": "go-webhook-server", + "version": "1.0.0", + }) +} + +func main() { + // Настраиваем Gin + gin.SetMode(gin.ReleaseMode) + router := gin.Default() + + // Добавляем middleware для логирования + router.Use(gin.LoggerWithFormatter(func(param gin.LogFormatterParams) string { + logger.WithFields(logrus.Fields{ + "status": param.StatusCode, + "latency": param.Latency, + "client_ip": param.ClientIP, + "method": param.Method, + "path": param.Path, + "user_agent": param.Request.UserAgent(), + }).Info("HTTP Request") + return "" + })) + + // Роуты + router.POST("/webhook", webhookHandler) + router.GET("/health", healthHandler) + + // Запуск сервера + addr := ":" + config.Port + logger.Infof("Starting webhook server on port %s", config.Port) + + server := &http.Server{ + Addr: addr, + Handler: router, + } + + // Graceful shutdown + go func() { + if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed { + logger.Fatalf("Failed to start server: %v", err) + } + }() + + // Ожидаем сигнал для graceful shutdown + <-make(chan os.Signal, 1) + logger.Info("Shutting down server...") + + ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second) + defer cancel() + + if err := server.Shutdown(ctx); err != nil { + logger.Errorf("Server forced to shutdown: %v", err) + } + + logger.Info("Server exited") +}