diff --git a/Makefile b/Makefile index ef62d66..49f02d5 100644 --- a/Makefile +++ b/Makefile @@ -43,7 +43,7 @@ back-build: embed-ui back-deps cd $(BACK_DIR) && go build -o knocker-serve . run: back-build - cd $(BACK_DIR) && GO_KNOCKER_SERVE_PASS=$(PASS) GO_KNOCKER_SERVE_PORT=$(PORT) ./knocker-serve serve + cd $(BACK_DIR) && GO_KNOCKER_SERVE_PASS=$(PASS) GO_KNOCKER_SERVE_PORT=$(PORT) ./knocker-serve -v serve run-bg: back-build cd $(BACK_DIR) && nohup env GO_KNOCKER_SERVE_PASS=$(PASS) GO_KNOCKER_SERVE_PORT=$(PORT) ./knocker-serve serve > /tmp/knocker.log 2>&1 & echo $$! && sleep 1 && tail -n +1 /tmp/knocker.log | sed -n '1,60p' diff --git a/article/embed-gui-guide.md b/article/embed-gui-guide.md index e96df86..9d44ef1 100644 --- a/article/embed-gui-guide.md +++ b/article/embed-gui-guide.md @@ -12,19 +12,22 @@ version: 1.0.0 ## Содержание -1. [Введение](#введение) -2. [Идея и архитектура](#идея-и-архитектура) -3. [Минимальный GUI](#минимальный-gui) -4. [Сборка фронтенда](#сборка-фронтенда) -5. [Встраивание в Go-сервис](#встраивание-в-go-сервис) -6. [API: контракт и примеры](#api-контракт-и-примеры) -7. [Запуск и проверка](#запуск-и-проверка) -8. [FAQ и типичные ошибки](#faq-и-типичные-ошибки) +- [Встраиваем веб-GUI в консольную утилиту: практический гайд](#встраиваем-веб-gui-в-консольную-утилиту-практический-гайд) + - [Содержание](#содержание) + - [Введение](#введение) + - [Идея и архитектура](#идея-и-архитектура) + - [Минимальный GUI](#минимальный-gui) + - [Сборка фронтенда](#сборка-фронтенда) + - [Встраивание в Go-сервис](#встраивание-в-go-сервис) + - [API: контракт и примеры](#api-контракт-и-примеры) + - [Запуск и проверка](#запуск-и-проверка) + - [FAQ и типичные ошибки](#faq-и-типичные-ошибки) ## Введение Задача: добавить простой веб-интерфейс к консольной утилите, собрать его один раз и отдавать статические файлы прямо из бинарника или из каталога рядом. В качестве примера используем минимальный UI со следующими полями: -- Targets (строка вида `tcp:host:port;udp:host:port`) + +- Targets (строка вида `tcp:host:port;udp:host:port...`) - Delay (например `1s`) - Флаг Wait connection - Кнопка Execute @@ -42,6 +45,7 @@ version: 1.0.0 Мы упростили компонент до минимума в ветке `for-article`. Основной экран — одна форма и кнопка запуска. Ключевой шаблон компонента: + ```12:60:/home/su/projects/articles/embed-gui-article/ui/src/app/knock/knock-page.component.html
@@ -69,6 +73,7 @@ version: 1.0.0 ``` Логика отправки запроса: + ```1:40:/home/su/projects/articles/embed-gui-article/ui/src/app/knock/knock-page.component.ts this.http.post('/api/v1/knock-actions/execute', { targets: v.targets, @@ -82,6 +87,7 @@ this.http.post('/api/v1/knock-actions/execute', { Собираем Angular-приложение и копируем артефакты в каталог, из которого Go-сервис будет обслуживать статику. Скрипт сборки: + ```1:20:/home/su/projects/articles/embed-gui-article/ui/build-for-embeding.sh #!/bin/bash # Использование: ./build-for-embeding.sh /abs/path/to/back/cmd/public @@ -96,12 +102,14 @@ cp -r /home/su/projects/angular/project-front/dist/project-front/browser/* "$DES ## Встраивание в Go-сервис Вариант А: раздача файлов из папки (`back/cmd/public`). + - Положите файлы фронта в `back/cmd/public`. - Убедитесь, что роутер отдаёт `/` и `/*` из этой папки. Вариант Б: `embed.FS` в бинарник. Пример (концептуально): + ```go package main @@ -128,6 +136,7 @@ func main() { Endpoint: `POST /api/v1/knock-actions/execute` Request JSON: + ```json { "targets": "tcp:127.0.0.1:22;udp:1.2.3.4:53", @@ -137,11 +146,13 @@ Request JSON: ``` Response 200: + ```json { "status": "ok" } ``` Пример curl: + ```bash curl -X POST http://localhost:8080/api/v1/knock-actions/execute \ -H 'Content-Type: application/json' \ @@ -150,17 +161,19 @@ curl -X POST http://localhost:8080/api/v1/knock-actions/execute \ ## Запуск и проверка -1) Собрать фронт и скопировать файлы: +1 Собрать фронт и скопировать файлы: + ```bash cd /home/su/projects/articles/embed-gui-article/ui ./build-for-embeding.sh /home/su/projects/articles/embed-gui-article/back/cmd/public ``` -2) Запустить бэкенд (варианты): +2 Запустить бэкенд (варианты): + - Через make/скрипт, если есть. - Локально `go run ./back` (или соответствующая команда в вашем проекте). -3) Открыть в браузере `/` и проверить, что форма грузится, а `Execute` бьёт в API. +3 Открыть в браузере `/` и проверить, что форма грузится, а `Execute` бьёт в API. ## FAQ и типичные ошибки diff --git a/article/struct-exaple.md b/article/struct-exaple.md deleted file mode 100644 index 46c9158..0000000 --- a/article/struct-exaple.md +++ /dev/null @@ -1,1884 +0,0 @@ -# Настройка Gitea Actions для Go проекта: Полное руководство - -```metadata -id: 4 -readTime: 15-20 минут -date: 2025-07-27 18:00 -author: Direct-Dev(aka Антон Кузнецов) -level: Средний -tags: #gitea #gitea-actions #ci-cd #go #docker #devops #automation #k3s -version: 1.0.0 -``` - -## Содержание - -1. [Введение](#введение) -2. [Подготовка проекта](#подготовка-проекта) -3. [Настройка Gitea Actions](#настройка-gitea-actions) -4. [Создание workflow файла](#создание-workflow-файла) -5. [Настройка секретов](#настройка-секретов) -6. [Тестирование и запуск](#тестирование-и-запуск) -7. [Мониторинг и отладка](#мониторинг-и-отладка) -8. [Заключение](#заключение) -A. [Инфраструктура](#инфраструктура) -B. [Установка Gitea в кластере K3s](#установка-gitea-в-кластере-k3s) -C. [Настройка Gitea Runner в LXC контейнере](#настройка-gitea-runner-в-lxc-контейнере) - -## Введение - -Gitea Actions — это встроенная система непрерывной интеграции и развертывания (CI/CD) в Gitea, которая позволяет автоматизировать процессы сборки, тестирования и развертывания ваших проектов. Большой плюс этой системы в том, что она достаточна не требовательна к ресурсам и может быть развернута в собственном изолированном окружении. - -В этой статье мы рассмотрим как работать с данной системой, на примере настройки Gitea Actions для Go проекта с автоматической сборкой мультиплатформенных бинарников, созданием Docker образов и публикацией релизов. - -### Итак, что мы будем делать - -- Настроим автоматическую сборку Go приложения для разных платформ -- Создадим Docker образы для Linux AMD64 и ARM64 -- Настроим публикацию в Docker Hub -- Автоматизируем создание релизов с бинарниками -- Сделаем коммит в отдельную ветку - которую можно связать с CD системой (flux/ArgoCD) -- Настроим триггеры на основе Git тегов - -## Подготовка проекта - -### Структура проекта - -Собственно функциональная часть проекта Go не блещет оригинальностью и имеет следующую простую структуру: - -``` text -hello_gitea/ -├── .gitea/ # Конфигурация Gitea Actions - └── workflows/ - └── build.yaml # Workflow для сборки -├── main.go # Основной код приложения -├── go.mod # Зависимости Go -├── go.sum # Хеши зависимостей -├── Dockerfile # Docker образ -├── Dockerfile.builder # Docker образ для образа-билдера проекта -├── scripts/ # Вспомогательные скрипты - └── release-interactive.sh # Скрипт для пушинга релиза -├── README.md # Документация -└── .gitignore # Исключения Git -``` - -### Анализ кода - -Для упрощения востприятия не применяются сложные архитектурные паттерны и концепции - нам надо просто минимальное Go приложение. Тем не менее это работоспособный http api сервер, который можно расширить парой тройкой эндпойнтов и использовать в других проектах как заглушку или тестовый api. - -Основная задача статьи не в написании api сервера на Go, а в автоматизации процесса его сборки. - -Наше приложение — это простой REST API сервер на Go с использованием Gin framework: - -```go -package main - -import ( - "net/http" - "os" - "github.com/gin-gonic/gin" -) - -const version = "1.0.0" - -func main() { - gin.SetMode(gin.ReleaseMode) - r := gin.Default() - - // CORS middleware - r.Use(func(c *gin.Context) { - c.Header("Access-Control-Allow-Origin", "*") - c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS") - c.Header("Access-Control-Allow-Headers", "Origin, Content-Type, Content-Length, Accept-Encoding, X-CSRF-Token, Authorization") - - if c.Request.Method == "OPTIONS" { - c.AbortWithStatus(http.StatusNoContent) - return - } - c.Next() - }) - - // Endpoints - r.GET("/healthz", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "status": "ok", - "version": version, - }) - }) - - r.GET("/", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "message": "Hello, World!", - "version": version, - }) - }) - - // API group - api := r.Group("/api/v1") - { - api.GET("/info", func(c *gin.Context) { - c.JSON(http.StatusOK, gin.H{ - "service": "hello-api", - "version": version, - "status": "running", - }) - }) - - api.POST("/echo", func(c *gin.Context) { - var request struct { - Message string `json:"message"` - } - - if err := c.ShouldBindJSON(&request); err != nil { - c.JSON(http.StatusBadRequest, gin.H{ - "error": "Invalid JSON", - }) - return - } - - c.JSON(http.StatusOK, gin.H{ - "echo": request.Message, - "version": version, - }) - }) - } - - port := os.Getenv("PORT") - if port == "" { - port = "8080" - } - - r.Run(":" + port) -} -``` - -### Dockerfile - -Помимо того, что мы будем собирать бинарники для разных платформ, мы также настроим сборку docker image в котором будем -запускать наш сервер api - это может быть полезным для развертывания нашего приложения, если мы настроим такое - например -через ArgoCD или flux (впрочем это тема отдельнйо статьи). - -Итак для контейнеризации используем многоэтапную сборку, что как вещают мудрецы является якобы полезным и правильным. -Не будем спорить поэтому вот: - -```dockerfile -# Build stage -FROM --platform=$BUILDPLATFORM golang:1.21-alpine AS builder - -RUN apk --no-cache add git ca-certificates -WORKDIR /app - -COPY go.mod go.sum ./ -RUN go mod download - -COPY . . - -ARG TARGETPLATFORM -ARG BUILDPLATFORM -ARG TARGETOS -ARG TARGETARCH - -RUN CGO_ENABLED=0 GOOS=${TARGETOS} GOARCH=${TARGETARCH} go build -a -installsuffix cgo -o hello-api main.go - -# Final stage -FROM alpine:latest - -RUN apk --no-cache add ca-certificates jq - -RUN addgroup -g 1001 -S appgroup && \ - adduser -u 1001 -S appuser -G appgroup - -WORKDIR /app -COPY --from=builder /app/hello-api . -RUN chown appuser:appgroup hello-api - -USER appuser -EXPOSE 8080 -CMD ["./hello-api"] -``` - -## Настройка Gitea Actions - -### Включение Actions в Gitea - -1. **Проверьте версию Gitea** - - Gitea Actions доступны начиная с версии 1.17.0. Убедитесь, что ваш сервер Gitea поддерживает Actions. - -2. **Включите Actions в настройках репозитория** - - - Перейдите в настройки репозитория - - Найдите раздел "Actions" - - Включите "Enable Actions" - -### Создание токена доступа - -1. **Создайте токен для Actions** - - - Перейдите в настройки профиля → "Applications" - - Создайте новый токен с правами на репозиторий - - Скопируйте токен (он понадобится позже) - -## Создание workflow файла - -### Структура директории - -Создайте директорию `.gitea/workflows/` в корне вашего проекта: - -```bash -mkdir -p .gitea/workflows -``` - -### Основной workflow файл - -Создайте файл `.gitea/workflows/build.yaml`: - -```yaml -name: Conditional Release Build -on: - push: - tags: - - v* - -jobs: - debug-conditions: - runs-on: ubuntu-latest - steps: - - name: Show build conditions - run: | - echo "Build conditions:" - echo " BUILD_CREATE_RELEASE: ${{ vars.BUILD_CREATE_RELEASE == 'true' }}" - echo " BUILD_CREATE_DOCKER_IMAGE: ${{ vars.BUILD_CREATE_DOCKER_IMAGE == 'true' }}" - echo " BUILD_UPDATE_RELEASE_BRANCH: ${{ vars.BUILD_UPDATE_RELEASE_BRANCH == 'true' }}" - - create-release: - runs-on: ubuntu-latest - if: ${{ vars.BUILD_CREATE_RELEASE == 'true' }} - container: - image: ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:latest - steps: - - name: Checkout repository - run: | - git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea - cd hello_gitea - git checkout ${{ github.ref }} - - - name: Setup Go - run: | - git --version - go version - jq --version - - - name: Build all binaries - run: | - cd hello_gitea - - # Проверяем, изменились ли зависимости - if [ ! -f go.sum ] || ! go mod verify >/dev/null 2>&1; then - echo "Dependencies changed, downloading..." - go mod download - fi - - mkdir -p bin - echo "Building for all platforms..." - - # Build for all platforms using direct go build commands - echo "Building for linux amd64..." - GOOS=linux GOARCH=amd64 go build -o bin/hello-api-linux-amd64 main.go - - echo "Building for linux arm64..." - GOOS=linux GOARCH=arm64 go build -o bin/hello-api-linux-arm64 main.go - - echo "Building for windows amd64..." - GOOS=windows GOARCH=amd64 go build -o bin/hello-api-windows-amd64.exe main.go - - echo "Building for darwin amd64..." - GOOS=darwin GOARCH=amd64 go build -o bin/hello-api-darwin-amd64 main.go - - echo "Building for darwin arm64..." - GOOS=darwin GOARCH=arm64 go build -o bin/hello-api-darwin-arm64 main.go - - # Create archives - echo "Creating archives..." - cd bin - - # Create archives with correct file names - tar -czf hello-api-linux-amd64.tar.gz hello-api-linux-amd64 - tar -czf hello-api-linux-arm64.tar.gz hello-api-linux-arm64 - tar -czf hello-api-windows-amd64.tar.gz hello-api-windows-amd64.exe - tar -czf hello-api-darwin-amd64.tar.gz hello-api-darwin-amd64 - tar -czf hello-api-darwin-arm64.tar.gz hello-api-darwin-arm64 - - echo "Listing bin directory..." - ls -la - - - name: Create Release - run: | - cd hello_gitea - # Create release using Gitea API - echo "Creating release..." - curl -X POST \ - -H "Authorization: token ${{ secrets.GITEATOKEN }}" \ - -H "Content-Type: application/json" \ - -d '{ - "tag_name": "${{ github.ref_name }}", - "name": "Release ${{ github.ref_name }}", - "body": "Automated release with multi-platform binaries and Docker image", - "draft": false, - "prerelease": false - }' \ - "https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases" - - echo "Getting release id..." - RELEASE_ID=$(curl -s -H "Authorization: token ${{ secrets.GITEATOKEN }}" \ - "https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases/tags/${{ github.ref_name }}" | \ - jq -r '.id') - - # Upload all binaries - echo "Uploading assets..." - for file in bin/*.tar.gz; do - echo "Uploading $file..." - curl -X POST \ - -H "Authorization: token ${{ secrets.GITEATOKEN }}" \ - -H "Content-Type: application/octet-stream" \ - --data-binary @$file \ - "https://direct-dev.ru/gitea/api/v1/repos/GiteaAdmin/hello_gitea/releases/$RELEASE_ID/assets?name=$(basename $file)" - done - - echo "Release created successfully" - - create-docker-image: - runs-on: ubuntu-latest - # needs: create-release - if: ${{ vars.BUILD_CREATE_DOCKER_IMAGE == 'true' }} - container: - image: docker:28.3.2-dind - steps: - - name: Checkout repository - run: | - apk add --no-cache git - - echo "=== GitHub Variables ===" - echo "github.ref = ${{ github.ref }}" - echo "github.ref_name = ${{ github.ref_name }}" - echo "github.sha = ${{ github.sha }}" - echo "github.repository = ${{ github.repository }}" - echo "========================" - git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea - cd hello_gitea - git checkout ${{ github.ref }} - - - name: Setup Docker Buildx - run: | - # Docker is already installed in docker:dind image - docker --version - echo "Setting up Docker Buildx for multi-platform builds..." - # Setup Docker Buildx for multi-platform builds - docker buildx create --name go-buildx --use - docker buildx inspect --bootstrap - - - name: Login to Docker Hub - run: | - echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin - - - name: Build multi-platform Docker images - run: | - cd hello_gitea - echo "Building multi-platform images using buildx..." - # Build multi-platform images using buildx - docker buildx build \ - --platform linux/amd64,linux/arm64 \ - --tag ${{ secrets.DOCKERHUB_USERNAME }}/hello-api:${{ github.ref_name }} \ - --tag ${{ secrets.DOCKERHUB_USERNAME }}/hello-api:latest \ - --push \ - . - echo "Multi-platform images built successfully" - - - name: Remove buildx - run: | - echo "Removing buildx..." - docker buildx rm go-buildx - - update-to-release-branch: - runs-on: ubuntu-latest - # needs: create-docker-image - if: ${{ vars.BUILD_UPDATE_RELEASE_BRANCH == 'true' }} - container: - image: docker:28.3.2-dind - steps: - - name: Create Release Branch - run: | - apk add --no-cache git - git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea - cd hello_gitea - - git config user.email "info@direct-dev.ru" - git config user.name "Direct-Dev-Robot" - - if git ls-remote --heads origin release | grep -q release; then - git checkout release - git pull origin release - else - git checkout -b release - fi - - git reset --hard ${{ github.ref_name }} - git push origin release --force -``` - -### Разберемся как работает workflow - -**Триггеры:** - -- Workflow запускается при пуше в Gitea тега, начинающегося с `v*` (например, `v1.1.29`) - -**Jobs:** - -у всех jobs имеется условный оператор выполнения -`if: ${{ vars.BUILD_UPDATE_RELEASE_BRANCH == 'true' }}` -то есть для того, чтобы тот или иной job запустился нужно установить соответствующую переменную в настройках репозитория в Gitea `https://direct-dev.ru/gitea/GiteaAdmin/hello_gitea/settings/actions/variables` - -1 **create-release:** - -- Эта таска запускается первой -- Управляется переменной BUILD_CREATE_RELEASE -- Использует контейнер собранный на базе golang:1.24 для сборки бинарников (image: ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:latest) -- Собирает бинарники для всех платформ (Linux, Windows, macOS) -- Создает архивы с бинарниками -- Создает релиз с именем текущей версии через Gitea API -- Загружает бинарники как assets релиза - -для работы данного job используется кастомный образ - если бы мы использовали просто golang:1.24, то при каждом запуске данного job необходимо было бы скачивать jq и устанавливаться в контейнере (возможно со временем, что-то еще потребовалось бы ...) - -```yaml - - name: Setup Go container - run: | - # Install jq for JSON parsing - apt-get update && apt-get install -y jq - git --version - go version - jq --version -``` - -**решение в лоб:** применено, чтобы убрать эту повторяющуюся работу при каждом выполнении workflow - сделан свой образ - "${DOCKERHUB_USERNAME}"/my-build-golang-runner:latest - -``` Dockerfile -# базовый образ -FROM golang:1.24 - -# Устанавливаем пакеты (одинаково работают на amd64/arm64) -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - git \ - ca-certificates \ - jq && \ - rm -rf /var/lib/apt/lists/* - -# Создаем рабочую директорию -WORKDIR /app - -# Копируем файлы зависимостей -COPY go.mod go.sum ./ - -# Предварительно загружаем все зависимости -RUN go mod download && go mod verify - -# Устанавливаем переменные окружения -ENV GOPATH=/go -ENV PATH=$PATH:/go/bin:/usr/local/bin - -# (Опционально) Можно добавить команду по умолчанию -CMD ["bash"] -``` - -Сначала собрал его и запушил (желательно с мультиплатформенностью) на каком-то локальном АРМ (не раннере) - -```bash -#!/bin/bash - -docker buildx build \ - --platform linux/amd64,linux/arm64 \ - --tag ${DOCKERHUB_USERNAME:-defaultdockeruser}/my-build-golang-runner:latest \ - --push \ - -f Dockerfile_for_runner_image \ - . -``` - -решение рабочее, но надо всегда иметь под рукой комп с buildx - лучше автоматизировать все и сделать задачу для сборки на раннере `.gitea/workflows/build-builder.yaml` - -```yaml -name: Build Builder Docker Image -on: - push: - tags: - - builder-* - -jobs: - create-builder-docker-image: - runs-on: ubuntu-latest - container: - image: docker:28.3.2-dind - steps: - - name: Checkout repository - run: | - # Install git - apk add --no-cache git - - echo "=== GitHub Variables ===" - echo "github.ref = ${{ github.ref }}" - echo "github.ref_name = ${{ github.ref_name }}" - echo "github.sha = ${{ github.sha }}" - echo "github.repository = ${{ github.repository }}" - echo "========================" - echo "Cloning..." - git clone https://oauth2:${{ secrets.GITEATOKEN }}@direct-dev.ru/gitea/GiteaAdmin/hello_gitea.git hello_gitea - cd hello_gitea - echo "Checkout to ${{ github.ref }} ..." - git checkout ${{ github.ref }} - - - name: Setup Docker Buildx - run: | - # Docker is already installed in docker:dind image - echo "look at docker version" - docker --version - # Setup Docker Buildx for multi-platform builds - echo "setup buildx" - docker buildx create --name builder-builx --use - docker buildx inspect --bootstrap - - - name: Login to Docker Hub - run: | - echo "login to docker hub ..." - echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin - - - name: Build multi-platform Docker images - run: | - cd hello_gitea - echo "Build multi-platform images using buildx ..." - docker buildx build \ - --platform linux/amd64,linux/arm64 \ - --tag ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:${{ github.ref_name }} \ - --tag ${{ secrets.DOCKERHUB_USERNAME }}/my-build-golang-runner:latest \ - --push \ - -f Dockerfile.builder \ - . - - - name: Remove buildx - run: | - docker buildx rm builder-builx -``` - -эта задача будет запущена на ранере при пуше тега с префиксом `builder-` - -2 **create-docker-image:** - создание образов docker с нашим проектом - -- Управляется переменной BUILD_CREATE_DOCKER_IMAGE -- Используем Docker-in-Docker контейнер (image: docker:28.3.2-dind) -- Настраиваем Docker Buildx для мультиплатформенной сборки (docker buildx create --use docker buildx inspect --bootstrap) -- Авторизуемся в Docker Hub (echo ${{ secrets.DOCKERHUB_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin) -- Собираем образы для Linux AMD64 и ARM64 (команда docker buildx build \ ...) -- Публикуем образы с тегом версии и `latest` (опция --push команды docker buildx build ...) - -чтобы авторизация сработала на докерхабе надо внести секреты DOCKERHUB_TOKEN, DOCKERHUB_USERNAME или вцелом для всего инстанса gitea в настройках инстанса или в настройках конкретного репозитория -я использовал докерхаб, но можно заморочиться и настроить работу с приватным репозиторием ... - -Уже после ряда тестовых релизо выяснил, что запуски workflow `build.yaml` да собственно как и `build-builder.yaml` порождают на хосте раннера зависшие докер контейнеры buildx - -```text - -СONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES -2a9ad63c5a31 moby/buildkit:buildx-stable-1 "buildkitd --allow-i…" 11 seconds ago Up 10 seconds buildx_buildkit_test-buldx0 -843eee192570 moby/buildkit:buildx-stable-1 "buildkitd --allow-i…" 22 minutes ago Up 22 minutes buildx_buildkit_modest_haibt0 - -``` - -Это происходит потому, что раннер запускает докер в докере контейнер также -docker run --privileged -it --rm -v /var/run/docker.sock:/var/run/docker.sock docker:28.3.2-dind sh -то есть пробрасывает хостовый сокет докер демона в контейнер и когда там выполняется `docker buildx create --use` контейнер запускается на хосте а не внутри docker:28.3.2-dind - -Со временем это может превратиться в утечку ресурсов - -Решения как минимум два - добавить параметр --name - `docker buildx create --name go-buildx --use` - тогда подхватится существующий контейнер или создастся новый. Второй способ удалять buildx `docker buildx rm --name go-buildx` - -3 **update-to-release-branch:** - создание ветки release в том случае если ее нет и вставка туда нашего коммита - далее по комиту в этой ветке можно настроить систему развертывания (например ArgoCD) - -- Управляется переменной BUILD_UPDATE_RELEASE_BRANCH -- можно добавить зависимость от create-docker-image если нужно `needs: create-docker-image` -- Используем Docker-in-Docker контейнер (image: docker:28.3.2-dind) - там уже есть git и образ скачан -- Пушим наш тег в ветку release (если ее нет она создастся) - -## Настройка секретов - -### Необходимые секреты - -В настройках репозитория (или инстанса) → "Secrets and variables" → "Actions" добавьте следующие секреты: - -1. **GITEATOKEN** - - Токен доступа к Gitea API - - Используется для клонирования репозитория и создания релизов - - Лучше задать на уровне репозитория `https://direct-dev.ru/gitea/GiteaAdmin/hello_gitea/settings/actions/secrets` - -2. **DOCKERHUB_USERNAME** - - Ваше имя пользователя в Docker Hub - - Используется для публикации образов - - Можно задать на уровне инстанса `https://direct-dev.ru/gitea/user/settings/actions/secrets` - -3. **DOCKERHUB_TOKEN** - - Токен доступа к Docker Hub - - Используется для авторизации в Docker Hub - - Можно задать на уровне инстанса `https://direct-dev.ru/gitea/user/settings/actions/secrets` - -### Создание токенов - -**Gitea Token:** - -1. Перейдите в настройки профиля → "Applications" -2. Создайте новый токен с правами на репозиторий -3. Скопируйте токен -4. В разделе Actions репозитория создайте секрет уровня репозитория - я создал с именем GITEATOKEN (такой не даст сделать: GITEA_TOKEN) - -**Docker Hub Token:** - -1. Войдите в Docker Hub -2. Перейдите в Account Settings → Security -3. Создайте новый Access Token -4. Скопируйте токен -5. В разделе Actions настроек инстанса gitea создайте секреты уровня инстанса (если много пользователей работает в gitea, то можно внести в уровень репозитория) я создал с именами DOCKERHUB_USERNAME, DOCKERHUB_TOKEN - -## Тестирование и запуск - -### Локальное тестирование - -1 **Проверим синтаксис workflow:** - -ну если мы в ide, то наверное все хорошо и уже автоматом отформатировано ... -но тем не менее ... - - ```bash - # Убедитесь, что YAML синтаксис корректен - yamllint .gitea/workflows/build.yaml - ``` - -2 **Протестируем сборку локально:** - -пока ручное тестирование - - ```bash - # Сборка для текущей платформы - go build -o hello-api main.go - - # Сборка для других платформ - GOOS=linux GOARCH=amd64 go build -o hello-api-linux-amd64 main.go - GOOS=linux GOARCH=arm64 go build -o hello-api-linux-arm64 main.go - ``` - -3 **Тестирование Docker образа:** - - ```bash - # Сборка образа - docker build -t hello-api:test . - - # Запуск контейнера - docker run -p 8080:8080 hello-api:test - - # Тестирование API - curl http://localhost:8080/healthz - ``` - -тут надо погонять curl по эндпойнтам, убедиться что приложение работает (пока тоже вручную) - -### Запуск Actions - -1 **Создаем тег вручную или скриптом (`scripts/release-interactive.sh`):** - - ```bash - - git tag v1.1.20 - git push origin v1.1.20 - - # текст скрипта - запуск можно сделать через make - #release-interactive: - # @./scripts/release-interactive.sh - - #!/bin/bash - - # Интерактивный скрипт для автоматизации релиза - # Использование: ./scripts/release-interactive.sh [version] - # Если версия не указана, скрипт запросит её интерактивно - - set -e # Остановить выполнение при ошибке - - # Функция для получения версии интерактивно - get_version_interactive() { - echo "🚀 Создание нового релиза" - echo "" - - # Показываем текущую версию - CURRENT_VERSION=$(grep 'const version = "' main.go | sed 's/const version = "\([^"]*\)"/\1/') - echo "📋 Текущая версия: $CURRENT_VERSION" - echo "" - - # Запрашиваем новую версию - read -r -p "Введите новую версию (формат X.Y.Z): " VERSION - - # Проверяем, что версия не пустая - if [ -z "$VERSION" ]; then - echo "❌ Версия не может быть пустой" - exit 1 - fi - - # Проверяем формат версии - if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "❌ Неверный формат версии. Используйте формат X.Y.Z (например, 1.0.25)" - exit 1 - fi - - # Подтверждение - echo "" - echo "📝 Подтверждение:" - echo " Текущая версия: $CURRENT_VERSION" - echo " Новая версия: $VERSION" - echo "" - read -r -p "Продолжить? (y/N): " CONFIRM - - if [[ ! $CONFIRM =~ ^[Yy]$ ]]; then - echo "❌ Релиз отменен" - exit 0 - fi - } - - # Проверяем, передана ли версия как аргумент - if [ $# -eq 0 ]; then - # Версия не указана, запрашиваем интерактивно - get_version_interactive - else - # Версия указана как аргумент - VERSION=$1 - - # Проверка формата версии - if [[ ! $VERSION =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then - echo "Ошибка: Неверный формат версии. Используйте формат X.Y.Z (например, 1.0.25)" - exit 1 - fi - fi - - echo "🚀 Начинаем релиз версии v$VERSION..." - - # Проверяем, что мы в git репозитории - if ! git rev-parse --git-dir > /dev/null 2>&1; then - echo "Ошибка: Не найден git репозиторий" - exit 1 - fi - - # Проверяем, что нет незакоммиченных изменений - # if ! git diff-index --quiet HEAD --; then - # echo "Ошибка: Есть незакоммиченные изменения. Сначала закоммитьте их." - # exit 1 - # fi - - # Обновляем версию в main.go - echo "📝 Обновляем версию в main.go..." - if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS - sed -i '' "s/const version = \"[^\"]*\"/const version = \"$VERSION\"/" main.go - else - # Linux - sed -i "s/const version = \"[^\"]*\"/const version = \"$VERSION\"/" main.go - fi - - # Проверяем, что изменение применилось - if ! grep -q "const version = \"$VERSION\"" main.go; then - echo "Ошибка: Не удалось обновить версию в main.go" - exit 1 - fi - - echo "✅ Версия обновлена в main.go" - - # Обновляем версию в makefile - echo "📝 Обновляем версию в makefile..." - if [[ "$OSTYPE" == "darwin"* ]]; then - # macOS - sed -i '' "s/^VERSION=.*/VERSION=$VERSION/" makefile - else - # Linux - sed -i "s/^VERSION=.*/VERSION=$VERSION/" makefile - fi - - # Проверяем, что изменение применилось - if ! grep -q "^VERSION=$VERSION" makefile; then - echo "Ошибка: Не удалось обновить версию в makefile" - exit 1 - fi - - echo "✅ Версия обновлена в makefile" - - # Выполняем git команды - echo "📦 Добавляем изменения в git..." - git add . - - echo "💾 Создаем коммит..." - git commit -m "Release v$VERSION" - - echo "🏷️ Создаем тег..." - git tag -a "v$VERSION" -m "Release v$VERSION" - - echo "🚀 Отправляем изменения и теги..." - git push - git push --tags - - echo "🎉 Релиз v$VERSION успешно завершен!" - echo "📋 Выполненные действия:" - echo " - Обновлена версия в main.go" - echo " - Обновлена версия в makefile" - echo " - Создан коммит с сообщением 'Release v$VERSION'" - echo " - Создан тег v$VERSION" - echo " - Изменения отправлены в удаленный репозиторий" - - ``` - -2 **Мониторинг выполнения:** - -- Переходим в репозиторий → "Actions" -- Находим запущенный workflow -- Отслеживаем выполнение каждого job - -### Ожидаемый результат - -После успешного выполнения: - -1 **Релиз** будет создан в Gitea с бинарниками: - -- `hello-api-linux-amd64.tar.gz` -- `hello-api-linux-arm64.tar.gz` -- `hello-api-windows-amd64.tar.gz` -- `hello-api-darwin-amd64.tar.gz` -- `hello-api-darwin-arm64.tar.gz` - -2 **Docker образы** будут опубликованы в Docker Hub: - -- `username/hello-api:v1.1.20` -- `username/hello-api:latest` - -3 **В ветке relese** появится новый коммит - на него можно, например, настроить деплой ArgoCD/flux в кластере k3s - -## Мониторинг и отладка - -### Просмотр логов - -1 **В Gitea:** - -- Переходим в репозиторий → "Actions" -- Выберем workflow -- Нажмем на job для просмотра логов - -2 **Отладка ошибок:** - -- Проверим правильность секретов -- Убедимся в корректности путей к репозиторию -- Проверим права доступа токенов - -### Частые проблемы - -1 **Ошибка авторизации в Docker Hub:** - -- Проверим правильность `DOCKERHUB_TOKEN` -- Убедимся, что токен не истек - -2 **Ошибка создания релиза:** - -- Проверим права токена `GITEATOKEN` -- Убедимся, что тег не существует - -3 **Ошибка сборки:** - -- Проверим зависимости в `go.mod` -- Убедимся в корректности Dockerfile - -## Заключение - -В этой статье мы рассмотрели полный процесс настройки Gitea Actions для Go проекта. Мы создали автоматизированный pipeline, который: - -- ✅ Собирает мультиплатформенные бинарники -- ✅ Создает Docker образы для разных архитектур -- ✅ Публикует образы в Docker Hub -- ✅ Создает релизы с бинарниками -- ✅ Запускается автоматически при создании тегов - -### Преимущества такого подхода - -1. **Автоматизация:** Минимизация ручной работы -2. **Консистентность:** Одинаковые условия сборки -3. **Мультиплатформенность:** Поддержка разных ОС и архитектур -4. **Безопасность:** Использование секретов для токенов -5. **Масштабируемость:** Легко добавить новые платформы или этапы - -### Следующие шаги - -1. тестирование в workflow -2. автоматическое развертывание -3. уведомления о результатах сборки -4. мониторинг и алерты - -Gitea Actions предоставляет мощные возможности для автоматизации процессов разработки, и с правильной настройкой вы можете значительно упростить процесс доставки вашего ПО. - ---- - -## Инфраструктура - -### Архитектура системы - -Этот и последующие разделы описывают инфраструктуру, на которой я проверял все описанное выше. -Если у вас используются другие подходы, то можете пропустить чтение этих разделов -или ознакомиться для общего развития. - -Итак инфраструктура состоит из следующих компонентов: - -``` text -Proxmox VE (ARM64) -├── K3s Cluster - ├── Master Node - └── Gitea Server (Helm Chart) - └── Worker Nodes -└── LXC Containers - └── Gitea Runner Container - ├── Docker Engine - ├── Go Toolchain - └── Build Tools -``` - -### Требования к системе - -**Proxmox VE:** - -- ARM64 архитектура (orangepi 5 Plus) -- Минимум 8GB RAM (16Gb) -- 100GB свободного места (1 Gb) -- Поддержка LXC контейнеров (+) - -**K3s Cluster:** - -- Kubernetes 1.24+ -- Helm 3.8+ -- Ingress Controller (Traefik) -- Persistent Storage (NFS) - -**LXC Container:** - -- Ubuntu 22.04 LTS -- 4GB RAM -- 20GB дискового пространства -- Docker Engine - -## Установка Gitea в кластере K3s - -### Анализ существующего кластера - -Текущая конфигурация: - -**Узлы кластера:** - -```bash -kubectl get nodes -o wide -NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP OS-IMAGE KERNEL-VERSION CONTAINER-RUNTIME -k3s-control-01 Ready control-plane,etcd,master 544d v1.33.2+k3s1 10.xxx.x.2 10.xx.x.2 Ubuntu 20.04.6 LTS 5.10.160-rockchip-rk3588 containerd://2.0.5-k3s1 -k3s-control-02 Ready control-plane,etcd,master 544d v1.33.2+k3s1 10.xxx.x.3 10.xx.x.3 Ubuntu 20.04.6 LTS 5.10.160-rockchip-rk3588 containerd://2.0.5-k3s1 -k3s-control-03 Ready control-plane,etcd,master 544d v1.33.2+k3s1 10.xxx.x.4 10.xx.x.4 Ubuntu 20.04.6 LTS 6.1.31-sun50iw9 containerd://2.0.5-k3s1 -``` - -**Существующие компоненты:** - -- Kubernetes v1.33.2+k3s1 -- 3 узла control-plane с etcd -- Traefik Ingress Controller -- NFS Storage Class для persistent storage -- Cert-Manager для SSL сертификатов -- Gitea уже установлен и работает - -### Проверка существующей установки Gitea - -```bash -# Проверка подов Gitea -kubectl get pods -n gitea -NAME READY STATUS RESTARTS AGE -gitea-798c56b58f-bsp2h 1/1 Running 0 30h -gitea-postgres-0 2/2 Running 10 (10d ago) 357d -gitea-valkey-cluster-0 1/1 Running 0 30h -gitea-valkey-cluster-1 1/1 Running 0 30h -gitea-valkey-cluster-2 1/1 Running 0 30h - -# Проверка сервисов -kubectl get svc -n gitea -NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE -gitea-http ClusterIP None 3000/TCP 543d -gitea-postgres ClusterIP 10.xx.xx.xx 5432/TCP 543d -gitea-ssh ClusterIP None 22/TCP 543d -gitea-valkey-cluster ClusterIP 10.xx.xx.xx 6379/TCP 30h -gitea-valkey-cluster-headless ClusterIP None 6379/TCP,16379/TCP 30h - -# Проверка persistent storage -kubectl get pvc -n gitea -NAME STATUS VOLUME CAPACITY ACCESS MODES STORAGECLASS AGE -gitea-shared-storage Bound pvc-c869aea8-7b49-4b40-9b86-1a7fbe9b2ce8 10Gi RWO nfs 543d -pgdata-gitea-postgres-0 Bound pvc-c3405a46-594d-401f-91ae-e7398b3c5cc3 15Gi RWO nfs 543d -valkey-data-gitea-valkey-cluster-0 Bound pvc-a927baf5-b8f8-4c0b-b12f-68fbc63162d9 8Gi RWO nfs 30h -valkey-data-gitea-valkey-cluster-1 Bound pvc-b43a399b-fdf4-4c27-baf4-7056ecc4143b 8Gi RWO nfs 30h -valkey-data-gitea-valkey-cluster-2 Bound pvc-368f0ab9-7851-423f-afb9-443287c0c728 8Gi RWO nfs 30h -``` - -### Анализ конфигурации - -**Gitea версия:** 1.24.3 (rootless) -**База данных:** PostgreSQL с persistent storage (15Gi) -**Кэш:** Valkey cluster (Redis) с 3 репликами -**Storage:** NFS Storage Class -**Ingress:** Traefik - -### Настройка доступа к Gitea - -Доступ к gitea лучше сделать через ingress или на худой конец через port-forward: - -1 **Временный доступ через port-forward:** - -```bash -# Доступ к Gitea через port-forward -kubectl port-forward -n gitea svc/gitea-http 3000:3000 - -# В другом терминале проверьте доступ -curl http://localhost:3000 -``` - -2 **Создание IngressRoute для постоянного доступа:** - -```yaml -# gitea-ingressroute.yaml -# image: docker.io/traefik:v3.0.0 поэтому используется абстракция -# apiVersion: traefik.io/v1alpha1 -# kind: IngressRoute - -apiVersion: traefik.io/v1alpha1 -kind: IngressRoute -metadata: - annotations: - cert-manager.io/cluster-issuer: letsencrypt-dns-cloudflare - creationTimestamp: "2024-05-24T03:50:33Z" - name: gitea-https-route - namespace: gitea -spec: - entryPoints: - - websecure - routes: - - kind: Rule - match: Host(`direct-dev.ru`) && PathPrefix(`/gitea`) - middlewares: - - name: strip-gitea-prefix - services: - - name: gitea-http - port: 3000 - tls: - secretName: le-root-direct-dev-ru -``` - -```bash -# Применение ingressroute -KUBECONFIG=~/.kube/config_hlab kubectl apply -f gitea-ingress.yaml -``` - -Таким образом мой инстанс gitea доступен с внешнего адреса - -в /data/gitea/conf/app.ini - -[server] -ROOT_URL = -DOMAIN = direct-dev.ru - -### Конфигурация Gitea - -Gitea установлен через Helm. - -**Версия:** Gitea 1.24.3 (rootless) -**Архитектура:** - -- PostgreSQL для базы данных (развернут отдельно ) -- Valkey cluster (Redis) для кэширования -- NFS Storage Class для persistent storage - -```bash -helm repo add gitea-charts https://dl.gitea.io/charts/ -helm repo update - -kubectl create namespace gitea - -helm install gitea gitea-charts/gitea -n gitea -``` - -установка с кастомными значениями параметров - -``` bash -helm show values gitea-charts/gitea > gitea-values.yaml -#Отредактируйте файл (например, database или service). -helm install gitea gitea-charts/gitea -n gitea -f gitea-values.yaml -``` - -Обновление - -```bash -helm repo update -helm upgrade gitea gitea-charts/gitea -n gitea -f gitea-values.yaml -``` - -**Проверка конфигурации:** - -```bash -# Проверка Helm релиза -helm list -n gitea - -# Просмотр конфигурации -helm get values gitea -n gitea -``` - -```yaml -USER-SUPPLIED VALUES: -gitea: - admin: - existingSecret: xxxxxx - config: - cache: - ADAPTER: memory - database: - DB_TYPE: postgres - HOST: gitea-postgres.gitea.svc.cluster.local:5432 - NAME: gitea - PASSWD: xxxxxxx - SCHEMA: gitea - USER: xxxx - indexer: - ISSUE_INDEXER_TYPE: bleve - REPO_INDEXER_ENABLED: true - queue: - TYPE: level - server: - APP_DATA_PATH: /data - DOMAIN: direct-dev.ru - ENABLE_PPROF: false - HTTP_PORT: 3000 - PROTOCOL: http - ROOT_URL: https://direct-dev.ru/gitea - SSH_DOMAIN: git.k3s-cluster-01.direct-dev.ru - SSH_LISTEN_PORT: 2222 - SSH_PORT: 22 - service: - DISABLE_REGISTRATION: true - SHOW_REGISTRATION_BUTTON: false - session: - PROVIDER: db -persistence: - enabled: true - storageClass: nfs -postgresql: - enabled: false -postgresql-ha: - enabled: false -redis-cluster: - enabled: false -``` - -Как видите я организовал доступ через внешний IP к URL `https://direct-dev.ru/gitea`, то есть через префикс роута, опыт работы с actions показал, что лучше бы организовать было через поддомен третьего уровня: что то типа `https://gitea.direct-dev.ru` - в этом случае всякие разные предопределенные jobs типа checkout@v3 должны клонирование отрабатывать нормально. - -``` bash - -# Проверка секретов -kubectl get secrets -n gitea -``` - -### Настройка Actions в существующем инстансе Gitea - -1 **Проверка включения Actions:** - -```bash -# Проверка конфигурации Actions -kubectl exec -n gitea deployment/gitea -c gitea -- cat /data/gitea/conf/app.ini" -``` - -2 **Включение Actions через веб-интерфейс:** - -- Откройте Gitea через port-forward или ingress -- Перейдите в Site Administration → Actions -- Включите "Enable Actions" -- Настройте "Default Actions URL" (например, ) - -3 **Проверка работы Actions:** - -```bash -# Проверка логов Gitea на предмет ошибок Actions -kubectl logs -n gitea deployment/gitea | grep -i action -``` - -### Настройка DNS и SSL - -Инструкция по установке Cert-Manager не приводится - инструкций вагон и маленькая тележка - -нет смысла повторяться: - -```bash -# Проверка Cert-Manager -kubectl get pods -n cert-manager - -# Проверка ClusterIssuer -kubectl get clusterissuer -``` - -1. **Настройка DNS записи:** - -```text -Добавить A-запись в DNS (у меня сloudflare) -gitea.your-domain.com -> внешний (белый) Ip - за ним пробросы портов если нужно ... -чтобы в итоге запрос поступил на traefik -``` - -2 **Создание ClusterIssuer (если не существует):** - -```bash -# Создание ClusterIssuer для Let's Encrypt - лучше через dns resolver -cat < /dev/null - -# Установка Docker -apt update -apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin - -# Добавление пользователя в группу docker -usermod -aG docker $USER - -# Запуск Docker -systemctl enable docker -systemctl start docker - -# Проверка установки -docker --version -``` - -3 **Установка Go:** - -```bash -# Скачивание Go для ARM64 -wget https://go.dev/dl/go1.21.0.linux-arm64.tar.gz - -# Распаковка -tar -C /usr/local -xzf go1.21.0.linux-arm64.tar.gz - -# Настройка переменных окружения -echo 'export PATH=$PATH:/usr/local/go/bin' >> ~/.bashrc -echo 'export GOPATH=$HOME/go' >> ~/.bashrc -echo 'export GOROOT=/usr/local/go' >> ~/.bashrc -source ~/.bashrc - -# Проверка установки -go version -# Должно показать: go version go1.21.0 linux/arm64 - -# Создание рабочей директории -mkdir -p $HOME/go/{bin,src,pkg} -``` - -4 **Установка дополнительных инструментов:** - -```bash -# Установка build tools -apt install -y build-essential - -# Установка jq для JSON обработки -apt install -y jq - -# Установка yamllint для проверки YAML -apt install -y yamllint - -# Установка Docker Buildx -docker buildx version -``` - -### Установка Gitea Runner - -1. **Скачивание Gitea Runner:** - -```bash -# Определение архитектуры -ARCH=$(uname -m) -case $ARCH in - x86_64) ARCH=amd64 ;; - aarch64) ARCH=arm64 ;; - armv7l) ARCH=armv7 ;; -esac - -# Скачивание последней версии -# visit https://dl.gitea.com/act_runner/ copy link to version and arch you need - -wget https://dl.gitea.com/act_runner/0.2.12/act_runner-0.2.12-linux-arm64 - -# Переименование и установка -mv act_runner-0.2.12-linux-arm64 /usr/local/bin/act_runner -chmod +x /usr/local/bin/act_runner - -# Проверка установки -act_runner --version -``` - -2 **Создание пользователя для runner:** - -```bash -# Создание пользователя -useradd -m -s /bin/bash gitea-runner -usermod -aG docker gitea-runner - -# Создание директории для конфигурации -mkdir -p /opt/gitea-runner -chown gitea-runner:gitea-runner /opt/gitea-runner -``` - -3 **Настройка конфигурации runner:** - -```bash -# Переключение на пользователя runner -su - gitea-runner - -# Создание конфигурационного файла -cat > /opt/gitea-runner/config.yaml < config.yaml` to generate a config file. - -log: - # The level of logging, can be trace, debug, info, warn, error, fatal - level: info - -runner: - # Where to store the registration result. - file: .runner - # Execute how many tasks concurrently at the same time. - capacity: 1 - # Extra environment variables to run jobs. - envs: - A_TEST_ENV_NAME_1: a_test_env_value_1 - A_TEST_ENV_NAME_2: a_test_env_value_2 - - # Extra environment variables to run jobs from a file. - # It will be ignored if it's empty or the file doesn't exist. - env_file: .env - - # The timeout for a job to be finished. - # Please note that the Gitea instance also has a timeout (3h by default) for the job. - # So the job could be stopped by the Gitea instance if it's timeout is shorter than this. - timeout: 3h - - # The timeout for the runner to wait for running jobs to finish when shutting down. - # Any running jobs that haven't finished after this timeout will be cancelled. - shutdown_timeout: 0s - - # Whether skip verifying the TLS certificate of the Gitea instance. - insecure: true - - # The timeout for fetching the job from the Gitea instance. - fetch_timeout: 5s - - # The interval for fetching the job from the Gitea instance. - fetch_interval: 2s - - # The labels of a runner are used to determine which jobs the runner can run, and how to run them. - # Like: "macos-arm64:host" or "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest" - # Find more images provided by Gitea at https://gitea.com/docker.gitea.com/runner-images . - # If it's empty when registering, it will ask for inputting labels. - # If it's empty when execute `daemon`, will use labels in `.runner` file. - labels: - - "ubuntu-latest:docker://docker.gitea.com/runner-images:ubuntu-latest" - - "ubuntu-22.04:docker://docker.gitea.com/runner-images:ubuntu-22.04" - - "ubuntu-20.04:docker://docker.gitea.com/runner-images:ubuntu-20.04" - -cache: - # Enable cache server to use actions/cache. - enabled: true - # The directory to store the cache data. - # If it's empty, the cache data will be stored in $HOME/.cache/actcache. - dir: "" - # The host of the cache server. - # It's not for the address to listen, but the address to connect from job containers. - # So 0.0.0.0 is a bad choice, leave it empty to detect automatically. - host: "" - # The port of the cache server. - # 0 means to use a random available port. - port: 0 - # The external cache server URL. Valid only when enable is true. - # If it's specified, act_runner will use this URL as the ACTIONS_CACHE_URL rather than start a server by itself. - # The URL should generally end with "/". - external_server: "" - -container: - # Specifies the network to which the container will connect. - # Could be host, bridge or the name of a custom network. - # If it's empty, act_runner will create a network automatically. - network: "" - # Whether to use privileged mode or not when launching task containers (privileged mode is required for Docker-in-Docker). - privileged: false - # And other options to be used when the container is started (eg, --add-host=my.gitea.url:host-gateway). - options: - # The parent directory of a job's working directory. - # NOTE: There is no need to add the first '/' of the path as act_runner will add it automatically. - # If the path starts with '/', the '/' will be trimmed. - # For example, if the parent directory is /path/to/my/dir, workdir_parent should be path/to/my/dir - # If it's empty, /workspace will be used. - workdir_parent: - # Volumes (including bind mounts) can be mounted to containers. Glob syntax is supported, see https://github.com/gobwas/glob - # You can specify multiple volumes. If the sequence is empty, no volumes can be mounted. - # For example, if you only allow containers to mount the `data` volume and all the json files in `/src`, you should change the config to: - # valid_volumes: - # - data - # - /src/*.json - # If you want to allow any volume, please use the following configuration: - # valid_volumes: - # - '**' - valid_volumes: [] - # overrides the docker client host with the specified one. - # If it's empty, act_runner will find an available docker host automatically. - # If it's "-", act_runner will find an available docker host automatically, but the docker host won't be mounted to the job containers and service containers. - # If it's not empty or "-", the specified docker host will be used. An error will be returned if it doesn't work. - docker_host: "" - # Pull docker image(s) even if already present - force_pull: true - # Rebuild docker image(s) even if already present - force_rebuild: false - -host: - # The parent directory of a job's working directory. - # If it's empty, $HOME/.cache/act/ will be used. - workdir_parent: -EOF -``` - -### Регистрация Runner в Gitea - -1. **Получение токена регистрации:** - -- Войдите в веб-интерфейс Gitea -- Перейдите в Settings → Actions → Runners -- Нажмите "New Runner" -- Скопируйте токен регистрации - -2 **Регистрация runner:** - -```bash -# В контейнере LXC под пользователем gitea-runner -act_runner register \ - --instance https://gitea.your-domain.com \ - --token YOUR_REGISTRATION_TOKEN \ - --name "k3s-runner-arm64" \ - --labels "ubuntu-latest:docker://node:18,ubuntu-22.04:docker://node:18,self-hosted:docker://golang:1.21,arm64:docker://golang:1.21" \ - --no-interactive - -``` - -3 **Создание systemd сервиса:** - -```bash -# Создание файла сервиса -cat > /etc/systemd/system/gitea-runner.service < /opt/gitea-runner/monitor.sh <<'EOF' -#!/bin/bash - -LOG_FILE="/opt/gitea-runner/logs/monitor.log" -RUNNER_STATUS=$(systemctl is-active gitea-runner) - -echo "$(date): Runner status: $RUNNER_STATUS" >> $LOG_FILE - -if [ "$RUNNER_STATUS" != "active" ]; then - echo "$(date): Restarting gitea-runner service" >> $LOG_FILE - systemctl restart gitea-runner -fi - -# Проверка свободного места -DISK_USAGE=$(df /opt/gitea-runner | tail -1 | awk '{print $5}' | sed 's/%//') -if [ $DISK_USAGE -gt 80 ]; then - echo "$(date): High disk usage: ${DISK_USAGE}%" >> $LOG_FILE - # Очистка старых логов и кэша - find /opt/gitea-runner/logs -name "*.log" -mtime +7 -delete - docker system prune -f -fi -EOF - -chmod +x /opt/gitea-runner/monitor.sh -``` - -2 **Настройка cron для мониторинга:** - -```bash -# Добавление в crontab -echo "*/5 * * * * /opt/gitea-runner/monitor.sh" | crontab - -``` - -### Тестирование Runner - -1. **Проверка подключения:** - -```bash -# Проверка статуса runner в Gitea -curl -H "Authorization: token YOUR_GITEA_TOKEN" \ - https://gitea.your-domain.com/api/v1/actions/runners -``` - -2 **Создание тестового workflow:** - -```yaml -# .gitea/workflows/test-runner.yaml -name: Test Runner -# on: -# push: -# branches: [main] -on: - push: - tags: - - v* - -jobs: - test: - runs-on: self-hosted - steps: - - name: Checkout - run: | - git clone https://oauth2:${{ secrets.GITEATOKEN }}@gitea.your-domain.com/your-username/your-repo.git - cd your-repo - - - name: Test Go - run: | - go version - go mod download - go build -o test-app main.go - - - name: Test Docker - run: | - docker --version - docker run hello-world - - - name: Test ARM64 Build - run: | - # Тестирование сборки для ARM64 - GOOS=linux GOARCH=arm64 go build -o test-app-arm64 main.go - file test-app-arm64 - # Должно показать: ELF 64-bit LSB executable, ARM aarch64 -``` - -### Оптимизация производительности - -1. **Настройка Docker daemon:** - -```bash -# Создание daemon.json -cat > /etc/docker/daemon.json < /etc/systemd/system/gitea-runner.service < /opt/gitea-runner/backup.sh <<'EOF' -#!/bin/bash - -BACKUP_DIR="/opt/gitea-runner/backups" -DATE=$(date +%Y%m%d_%H%M%S) - -mkdir -p $BACKUP_DIR - -# Резервное копирование конфигурации -tar -czf $BACKUP_DIR/config_$DATE.tar.gz /opt/gitea-runner/config.yaml - -# Резервное копирование логов -tar -czf $BACKUP_DIR/logs_$DATE.tar.gz /opt/gitea-runner/logs/ - -# Удаление старых резервных копий (старше 30 дней) -find $BACKUP_DIR -name "*.tar.gz" -mtime +30 -delete - -echo "Backup completed: $DATE" -EOF - -chmod +x /opt/gitea-runner/backup.sh - -# Добавление в crontab (ежедневно в 2:00) -echo "0 2 * * * /opt/gitea-runner/backup.sh" | crontab - -``` - -## Работа с кластером - -### Основные команды для работы с кластером - -```bash -# Установка переменной окружения для работы с кластером -export KUBECONFIG=~/.kube/{configfile} - -# Проверка состояния кластера -kubectl get nodes -o wide -kubectl get pods -A - -# Работа с Gitea -kubectl get pods -n gitea -kubectl logs -n gitea deployment/gitea -kubectl port-forward -n gitea svc/gitea-http 3000:3000 - -# Проверка storage -kubectl get pvc -n gitea -kubectl get storageclass - -# Проверка ingress и сервисов -kubectl get svc -n default | grep traefik -kubectl get crds -kubectl get ingressroutes.traefik.io -A - -# Проверка сертификатов -kubectl get certificates -A -o wide -``` - -### Мониторинг и обслуживание - -```bash -# Проверка ресурсов кластера -kubectl top nodes -kubectl top pods -n gitea - -# Проверка событий -kubectl get events -n gitea --sort-by='.lastTimestamp' - -# Резервное копирование Gitea -kubectl exec -n gitea deployment/gitea -- gitea dump -c /data/gitea/conf/app.ini - -# Обновление Gitea -helm repo update -helm upgrade gitea gitea-charts/gitea -n gitea --values gitea-values.yaml -``` - -### Устранение неполадок - -```bash -# Проверка логов Gitea -kubectl logs -n gitea deployment/gitea -f - -# Проверка логов PostgreSQL -kubectl logs -n gitea statefulset/gitea-postgres -c postgres - -# Проверка логов Valkey -kubectl logs -n gitea statefulset/gitea-valkey-cluster -c valkey - -# Проверка сетевой связности -kubectl exec -n gitea deployment/gitea -- ping gitea-postgres -kubectl exec -n gitea deployment/gitea -- ping gitea-valkey-cluster -``` diff --git a/back/cmd/knock_routes.go b/back/cmd/knock_routes.go index 1b30229..e581941 100644 --- a/back/cmd/knock_routes.go +++ b/back/cmd/knock_routes.go @@ -71,7 +71,7 @@ func setupKnockRoutes(api *gin.RouterGroup) { } } - if err := knocker.ExecuteWithConfig(&config, req.Verbose, req.WaitConnection); err != nil { + if err := knocker.ExecuteWithConfig(&config, true || req.Verbose, req.WaitConnection); err != nil { c.JSON(400, gin.H{"error": err.Error()}) return } diff --git a/back/main.go b/back/main.go index 845fa06..d06f9fe 100644 --- a/back/main.go +++ b/back/main.go @@ -7,7 +7,7 @@ import ( "port-knocker/cmd" ) -// Version и BuildTime устанавливаются при сборке через ldflags + var ( Version = "v1.0.10" BuildTime = "unknown" diff --git a/back/scripts/quick-release.sh b/back/scripts/quick-release.sh index 8024937..0cb3133 100755 --- a/back/scripts/quick-release.sh +++ b/back/scripts/quick-release.sh @@ -112,6 +112,7 @@ git push origin main # Сборка бинарников log_info "Собираем бинарники для всех платформ..." export VERSION_NUM="${VERSION#v}" +# shellcheck disable=SC2155 export BUILD_TIME=$(date -u '+%Y-%m-%d_%H:%M:%S') # Функция сборки для платформы @@ -155,6 +156,7 @@ log_info "Создаем Git тег..." # Читаем release-notes.md и сохраняем содержимое в переменную NOTES NOTES=$(cat docs/scripts/release-notes.md) # Заменяем все переменные вида $VERSION в NOTES на их значения +# shellcheck disable=SC2001 NOTES=$(echo "$NOTES" | sed "s/\\\$VERSION/$VERSION/g") git tag -a "$VERSION" -m "$NOTES" diff --git a/knocker-serve b/knocker-serve deleted file mode 100755 index c959979..0000000 Binary files a/knocker-serve and /dev/null differ diff --git a/ui/src/app/app.routes.ts b/ui/src/app/app.routes.ts index 69181c5..6599deb 100644 --- a/ui/src/app/app.routes.ts +++ b/ui/src/app/app.routes.ts @@ -1,9 +1,8 @@ import { Routes } from '@angular/router'; import { BasicKnockPageComponent } from './basic-knock/basic-knock-page.component'; -import { FsaKnockPageComponent } from './fsa-knock/fsa-knock-page.component'; + export const routes: Routes = [ { path: '', component: BasicKnockPageComponent }, - { path: 'fsa', component: FsaKnockPageComponent }, { path: '**', redirectTo: '' } ]; \ No newline at end of file diff --git a/ui/src/app/basic-knock/basic-knock-page.component.ts b/ui/src/app/basic-knock/basic-knock-page.component.ts index f2be37d..cabf2c5 100644 --- a/ui/src/app/basic-knock/basic-knock-page.component.ts +++ b/ui/src/app/basic-knock/basic-knock-page.component.ts @@ -15,7 +15,7 @@ import { KnockPageComponent } from '../knock/knock-page.component'; template: `
- +
diff --git a/ui/src/app/fsa-knock/fsa-knock-page.component.ts b/ui/src/app/fsa-knock/fsa-knock-page.component.ts deleted file mode 100644 index 00c1c44..0000000 --- a/ui/src/app/fsa-knock/fsa-knock-page.component.ts +++ /dev/null @@ -1,132 +0,0 @@ -import { Component } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { RouterModule } from '@angular/router'; -import { CardModule } from 'primeng/card'; -import { ButtonModule } from 'primeng/button'; -import { DialogModule } from 'primeng/dialog'; -import { KnockPageComponent } from '../knock/knock-page.component'; - -@Component({ - selector: 'app-fsa-knock-page', - standalone: true, - imports: [ - CommonModule, RouterModule, CardModule, ButtonModule, DialogModule, KnockPageComponent - ], - template: ` -
-
-

File System Access API не поддерживается

-

Эта функциональность требует браузер с поддержкой File System Access API:

-
    -
  • Google Chrome 86+
  • -
  • Microsoft Edge 86+
  • -
  • Opera 72+
  • -
-

Ваш браузер: {{ browserInfo }}

- -
- -
- - -
-
- - - -
-

- Эта версия поддерживает прямое редактирование файлов на диске. - Файлы будут автоматически перезаписываться после шифрования/дешифрования. -

-
-

- ✅ Доступные возможности: -

-
    -
  • Прямое открытие файлов с диска
  • -
  • Автоматическое сохранение изменений
  • -
  • Перезапись зашифрованных файлов "на месте"
  • -
  • Быстрая работа без диалогов загрузки/скачивания
  • -
-
-
-
- `, - styles: [` - .container { - max-width: 1200px; - margin: 0 auto; - padding: 1rem; - } - - ul { - display: inline-block; - text-align: left; - } - - .info-link { - color: #3b82f6; - cursor: pointer; - text-decoration: none; - font-weight: 500; - transition: color 0.2s ease; - } - - .info-link:hover { - color: #1d4ed8; - text-decoration: underline; - } - - .bg-green-50 { - background-color: #f0fdf4; - } - - .dialog-content { - min-width: 450px; - } - `] -}) -export class FsaKnockPageComponent { - isFSASupported = false; - browserInfo = ''; - showInfoDialog = false; - - constructor() { - this.checkFSASupport(); - this.getBrowserInfo(); - } - - private checkFSASupport() { - const w = window as any; - this.isFSASupported = typeof w.showOpenFilePicker === 'function'; - } - - private getBrowserInfo() { - const ua = navigator.userAgent; - if (ua.includes('Chrome') && !ua.includes('Edg/')) { - this.browserInfo = 'Google Chrome'; - } else if (ua.includes('Edg/')) { - this.browserInfo = 'Microsoft Edge'; - } else if (ua.includes('Opera') || ua.includes('OPR/')) { - this.browserInfo = 'Opera'; - } else if (ua.includes('Firefox')) { - this.browserInfo = 'Mozilla Firefox'; - } else if (ua.includes('Safari') && !ua.includes('Chrome')) { - this.browserInfo = 'Safari'; - } else { - this.browserInfo = 'Неизвестный браузер'; - } - } -} diff --git a/ui/src/app/knock/knock-page.component.html b/ui/src/app/knock/knock-page.component.html index 67a200c..c36bfda 100644 --- a/ui/src/app/knock/knock-page.component.html +++ b/ui/src/app/knock/knock-page.component.html @@ -1,5 +1,5 @@
- +
@@ -8,7 +8,7 @@ pInputText type="text" formControlName="targets" - placeholder="tcp:host:port;udp:host:port" + placeholder="tcp:host:port;udp:host:port;...;tcp:host:port" class="w-full" />