# Gitea Complete Guide — local_machine

> **Этот гайд** — справочник по использованию Gitea для репозитория **local_machine** (документация и скрипты локальной машины).  
> Сервер: **gitea.hunab.app** (209.38.32.21).  
> **Быстрый старт для local_machine:** [LOCAL_MACHINE_GITEA_SETUP.md](./LOCAL_MACHINE_GITEA_SETUP.md).

> Для других репозиториев: **legal** на 206.189.35.205 — [GITEA_LEGAL_206_SERVER_GUIDE.md](./GITEA_LEGAL_206_SERVER_GUIDE.md); **hunabapp** — свои скрипты пуша в том репо.

---

**Дата:** 2026-01-02  
**Версия Gitea:** 1.25.3  
**Статус:** ✅ Полностью развернут и работает  
**Последнее обновление:** Адаптация под проект local_machine (2026-03-14)

---

## 📋 Обзор

Gitea успешно развернут на production сервере как self-hosted альтернатива GitHub.

### Преимущества
- ✅ Полный контроль данных (код на вашем сервере)
- ✅ Бесплатно для неограниченного числа репозиториев
- ✅ Нет лимитов на CI/CD минуты
- ✅ Низкая задержка (локальная сеть)
- ✅ Интеграция с существующей инфраструктурой

### Текущая конфигурация
- **Домен:** https://gitea.hunab.app
- **Сервер:** 209.38.32.21
- **HTTP порт:** 3000 (внутренний)
- **SSH порт:** 2223 (внешний)
- **База данных:** PostgreSQL (hunabgit) - **СТАНДАРТ**
- **SSL:** Let's Encrypt, TLSv1.2/TLSv1.3
- **Контейнер:** `gitea`
- **Сеть:** `hunab-network`

**⚠️ ВАЖНО:** PostgreSQL является стандартом для всех развертываний Gitea. MySQL и SQLite не используются.

---

## 🚀 Развертывание

### Быстрый старт для local_machine

Репозиторий **local_machine** не разворачивает Gitea — он только пушит в уже работающий сервер. Первая настройка и пуш:

```bash
cd /Users/eternal/code/local_machine
# 1. Создать репо на https://gitea.hunab.app (New Repository → local_machine)
# 2. Инициализация и пуш:
bash scripts/deployment/gitea/setup-and-push.sh
```

Подробно: **[LOCAL_MACHINE_GITEA_SETUP.md](./LOCAL_MACHINE_GITEA_SETUP.md)**.

### Развертывание самого Gitea (для админов сервера)

Скрипты развертывания Gitea на сервере находятся в других репозиториях (например hunabapp). Пример для справки:

```bash
# Развертывание Gitea (в репо с инфраструктурой)
bash scripts/deployment/gitea/deploy-gitea.sh
bash scripts/deployment/gitea/setup-gitea.sh
bash scripts/deployment/gitea/test-gitea.sh
```

### Docker Compose (для reference)

```yaml
version: "3"
services:
  gitea:
    image: gitea/gitea:1.25.3
    container_name: gitea
    restart: always
    networks:
      - hunab-network
    ports:
      - "3000:3000"
      - "2223:22"
    volumes:
      - /opt/gitea:/data
    environment:
      - USER_UID=1000
      - USER_GID=1000
```

---

## 🗄️ База данных PostgreSQL

**⚠️ СТАНДАРТ (ОБЯЗАТЕЛЬНО К СОБЛЮДЕНИЮ):** PostgreSQL является ЕДИНСТВЕННЫМ стандартом для всех развертываний Gitea. MySQL и SQLite ЗАПРЕЩЕНЫ.

### ✅ PostgreSQL - ОБЯЗАТЕЛЬНЫЙ СТАНДАРТ

- ✅ **Используется на основном сервере** (209.38.32.21)
- ✅ **Используется на зеркальном сервере** (206.189.35.205)
- ✅ **ОБЯЗАТЕЛЕН для всех новых развертываний**
- ✅ **ОБЯЗАТЕЛЕН для всех существующих развертываний** (миграция с MySQL/SQLite)
- ✅ Лучшая производительность для больших репозиториев
- ✅ Поддержка транзакций и ACID
- ✅ Масштабируемость и надежность

### ❌ MySQL и SQLite - ЗАПРЕЩЕНЫ

- ❌ **ЗАПРЕЩЕНО использовать MySQL** в проекте
- ❌ **ЗАПРЕЩЕНО использовать SQLite** в production
- ❌ Ошибка "dial tcp [::1]:3306" означает попытку подключения к MySQL - **НЕДОПУСТИМО**

### Стандартная конфигурация

**Основной сервер (209.38.32.21):**
```ini
[database]
DB_TYPE  = postgres
HOST     = hunab-prod-postgres:5432
NAME     = hunabgit
USER     = hunabgit
PASSWD   = YOUR_PASSWORD
SSL_MODE = disable
CHARSET  = utf8mb4
```

**Зеркальный сервер (206.189.35.205):**
```ini
[database]
DB_TYPE  = postgres
HOST     = gitea-mirror-postgres:5432
NAME     = hunabgit
USER     = utils  # ⚠️ На зеркальном сервере используется пользователь utils
PASSWD   = YOUR_PASSWORD
SSL_MODE = disable
CHARSET  = utf8mb4
```

**⚠️ КРИТИЧЕСКИ ВАЖНО:**
- ❌ Параметр `PATH` ЗАПРЕЩЕН (используется только для SQLite)
- ❌ `HOST = localhost:3306` ЗАПРЕЩЕН (это MySQL порт)
- ✅ Обязательно использовать имя контейнера PostgreSQL в Docker сети
- ⚠️ **На зеркальном сервере используется пользователь `utils` вместо `hunabgit`**

### Создание пользователя utils для зеркального сервера

**Проблема:** Если пользователь `utils` не существует или имеет неправильный пароль, Gitea не сможет подключиться к PostgreSQL.

**Решение:**

#### Автоматический скрипт (рекомендуется)

```bash
# На сервере 206.189.35.205
bash scripts/deployment/fix-postgres-utils-user.sh
```

#### Ручное создание пользователя

```bash
# Определение суперпользователя PostgreSQL
POSTGRES_SUPERUSER=$(docker inspect gitea-mirror-postgres | grep -oP '(?<="POSTGRES_USER=")[^"]*' | head -1 || echo "postgres")

# Если не найден, пробуем стандартные варианты
if [ -z "$POSTGRES_SUPERUSER" ] || [ "$POSTGRES_SUPERUSER" = "null" ]; then
    if docker exec gitea-mirror-postgres psql -U postgres -d postgres -c "SELECT 1;" > /dev/null 2>&1; then
        POSTGRES_SUPERUSER="postgres"
    elif docker exec gitea-mirror-postgres psql -U hunabgit -d postgres -c "SELECT 1;" > /dev/null 2>&1; then
        POSTGRES_SUPERUSER="hunabgit"
    else
        POSTGRES_SUPERUSER="postgres"
    fi
fi

# Создание или изменение пароля для utils
docker exec gitea-mirror-postgres psql -U $POSTGRES_SUPERUSER -d postgres << 'SQL'
DO $$
BEGIN
    IF NOT EXISTS (SELECT FROM pg_user WHERE usename = 'utils') THEN
        CREATE USER utils WITH PASSWORD 'YOUR_PASSWORD';
    ELSE
        ALTER USER utils WITH PASSWORD 'YOUR_PASSWORD';
    END IF;
END
$$;
SQL

# Предоставление прав
docker exec gitea-mirror-postgres psql -U $POSTGRES_SUPERUSER -d postgres -c "GRANT ALL PRIVILEGES ON DATABASE hunabgit TO utils;"
docker exec gitea-mirror-postgres psql -U $POSTGRES_SUPERUSER -d hunabgit -c "GRANT ALL PRIVILEGES ON ALL TABLES IN SCHEMA public TO utils;"
docker exec gitea-mirror-postgres psql -U $POSTGRES_SUPERUSER -d hunabgit -c "GRANT ALL PRIVILEGES ON ALL SEQUENCES IN SCHEMA public TO utils;"
docker exec gitea-mirror-postgres psql -U $POSTGRES_SUPERUSER -d hunabgit -c "GRANT CREATE ON SCHEMA public TO utils;"

# Проверка
docker exec gitea-mirror-postgres psql -U utils -d hunabgit -c "SELECT version();"
```

**Диагностика проблем:**

1. **"role \"utils\" does not exist"** - Пользователь не создан:
   ```bash
   docker exec gitea-mirror-postgres psql -U postgres -d postgres -c "CREATE USER utils WITH PASSWORD 'YOUR_PASSWORD';"
   ```

2. **"password authentication failed"** - Пароль неверный:
   ```bash
   docker exec gitea-mirror-postgres psql -U postgres -d postgres -c "ALTER USER utils WITH PASSWORD 'YOUR_PASSWORD';"
   ```

3. **"permission denied for database"** - Нет прав:
   ```bash
   docker exec gitea-mirror-postgres psql -U postgres -d postgres -c "GRANT ALL PRIVILEGES ON DATABASE hunabgit TO utils;"
   ```

**Проверка после создания:**
```bash
docker exec gitea-mirror-postgres psql -U utils -d hunabgit -c "SELECT 1;"
# Должно вернуть: 1 без ошибок
```

### Настройка через веб-интерфейс

При первом запуске http://209.38.32.21:3000:

**Database Settings (СТАНДАРТ - PostgreSQL):**
- **Database Type:** `PostgreSQL` ⚠️ **ОБЯЗАТЕЛЬНО PostgreSQL, НЕ MySQL, НЕ SQLite**
- **Host:** `hunab-prod-postgres:5432` (для основного сервера) или `gitea-mirror-postgres:5432` (для зеркального)
- **Database Name:** `hunabgit` ⚠️ **НЕ gitea!**
- **Username:** `hunabgit` (основной) или `utils` (зеркальный)
- **Password:** `YOUR_PASSWORD`
- **SSL Mode:** `disable`

**General Settings:**
- Domain: `gitea.hunab.app`
- SSH Port: `2223`
- HTTP Port: `3000`
- Gitea Base URL: `https://gitea.hunab.app`

**Administrator Account:**
- Username: `hunabgit`
- Email: `mikhevel@gmail.com`
- Password: (надежный пароль)

---

## 🔐 SSL Configuration

### Сертификат Let's Encrypt

```bash
# Получение сертификата
sudo certbot certonly --standalone -d gitea.hunab.app

# Копирование в рабочую директорию
sudo cp /etc/letsencrypt/live/gitea.hunab.app/fullchain.pem /opt/app/ssl/gitea-fullchain.pem
sudo cp /etc/letsencrypt/live/gitea.hunab.app/privkey.pem /opt/app/ssl/gitea-privkey.pem
```

### Nginx Configuration

Конфигурация находится в `docker/nginx/environments/gitea.conf`:

```nginx
# HTTP → HTTPS redirect
server {
    listen 80;
    server_name gitea.hunab.app;
    return 301 https://$host$request_uri;
}

# HTTPS server
# CRITICAL: HTTP/2 disabled - git operations require HTTP/1.1
server {
    listen 443 ssl;
    # http2 on;  # DISABLED: git operations don't work well with HTTP/2
    server_name gitea.hunab.app;
    
    # SSL configuration
    ssl_certificate /opt/app/ssl/gitea-fullchain.pem;
    ssl_certificate_key /opt/app/ssl/gitea-privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers HIGH:!aNULL:!MD5;

    # Gitea reverse proxy
    location / {
        proxy_pass http://gitea:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection 'upgrade';
        proxy_cache_bypass $http_upgrade;
        
        # Timeouts
        proxy_connect_timeout 60s;
        proxy_send_timeout 60s;
        proxy_read_timeout 60s;
        
        # Buffer settings
        proxy_buffering off;
        proxy_request_buffering off;
    }
    
    # Git operations (git-receive-pack, git-upload-pack) - увеличенные таймауты
    # CRITICAL: HTTP/2 disabled for git operations (git uses HTTP/1.1)
    location ~ ^/.*\.git/(git-receive-pack|git-upload-pack) {
        proxy_http_version 1.1;
        proxy_pass http://gitea:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        
        # CRITICAL: Use keep-alive for stable connection during data transfer
        proxy_set_header Connection "keep-alive";
        
        # Extended timeouts for git operations (для больших репозиториев)
        proxy_connect_timeout 300s;
        proxy_send_timeout 600s;
        proxy_read_timeout 600s;
        
        # Buffer settings (отключены для больших файлов)
        proxy_buffering off;
        proxy_request_buffering off;
        client_max_body_size 0;
        
        # Дополнительные настройки для стабильности
        proxy_redirect off;
        proxy_set_header Accept-Encoding "";
    }
    
    # Git info/refs operations - также с увеличенными таймаутами
    location ~ ^/.*\.git/info/refs {
        proxy_http_version 1.1;
        proxy_pass http://gitea:3000;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_set_header Connection "";
        
        # Extended timeouts
        proxy_connect_timeout 300s;
        proxy_send_timeout 600s;
        proxy_read_timeout 600s;
        
        # Buffer settings
        proxy_buffering off;
        proxy_request_buffering off;
        client_max_body_size 0;
    }
}
```

**Ключевые особенности:**
- ✅ HTTP/2 отключен для всего server блока (git требует HTTP/1.1)
- ✅ Правильные SSL сертификаты (gitea-fullchain.pem с SAN для gitea.hunab.app)
- ✅ Увеличенные таймауты для git операций (300-600s)
- ✅ Отключена буферизация для больших файлов
- ✅ Специальные location блоки для git операций

### SSL Optimization

- ✅ TLSv1.2 и TLSv1.3
- ✅ Современные cipher suites
- ✅ HSTS enabled (1 год)
- ✅ Security headers
- ✅ Session tickets disabled

**Рекомендация:** Добавьте DNS CAA запись:
```
Type: CAA
Name: gitea.hunab.app
Value: 0 issue "letsencrypt.org"
```

---

## 🔄 Push репозиториев (КРИТИЧЕСКИ ВАЖНО)

### ✅ Для репозитория local_machine

В этом проекте пуш делается из корня **local_machine**:

```bash
cd /Users/eternal/code/local_machine
git push -u origin main
```

**Если из РФ push по HTTPS зависает** — переключите remote на SSH (порт 2223):

```bash
git remote set-url origin ssh://git@gitea.hunab.app:2223/hunabgit/local_machine.git
git push -u origin main
```

Если и SSH недоступен — используйте [метод через bundle](#резервный-способ-push-через-bundle) ниже. Полная настройка: **[LOCAL_MACHINE_GITEA_SETUP.md](./LOCAL_MACHINE_GITEA_SETUP.md)**.

---

### ❌ Проблема: Push через HTTPS зависает

При попытке `git push` через HTTPS возникают таймауты:
```
fatal: unable to access 'https://gitea.hunab.app/...': Recv failure: Operation timed out
```

**Корневые причины:**
1. HTTP/2 несовместим с git операциями (git использует HTTP/1.1)
2. Недостаточные таймауты для больших репозиториев
3. Проблемы с keepalive соединениями через reverse proxy
4. Ограничения curl/git на стороне клиента (особенно на macOS)

**Диагностика в логах Gitea:**
```
Fail to serve RPC(receive-pack): exit status 128 
fatal: the remote end hung up unexpectedly
```

POST запрос доходит до Gitea, но соединение обрывается во время передачи данных.

### ✅ Универсальный скрипт пуша (в других репо)

В репозиториях с полной инфраструктурой (например hunabapp) используется скрипт, который автоматически выбирает метод (SSH → HTTPS → bundle):

```bash
# Обычный скрипт (для работы не из РФ)
bash scripts/deployment/gitea/git-push-gitea.sh [branch]

# 🇷🇺 ДЛЯ РФ: Скрипт через прокси (ускоряет в 5-10 раз)
bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh [branch]
```

**Для local_machine** такого скрипта нет — используйте `git push origin main` или SSH (см. выше).

**🇷🇺 Из РФ:**
- Предпочтительно **SSH** (порт 2223) — часто стабильнее HTTPS.
- Либо метод через bundle (см. раздел ниже).

### ✅ Резервный способ: Временный скрипт для коммита и пуша

**Когда использовать:**
- Терминал недоступен из-за проблем с zsh/shell
- Агент не может выполнить команды напрямую
- Нужно подготовить все команды заранее

**Процесс:**
1. **Агент создает временный скрипт** в `temp/commit-and-push.sh` с полным коммитом и push командой
2. **Пользователь выполняет скрипт** вручную в терминале
3. **Скрипт автоматически** делает `git add`, `git commit` и `git push`

**Формат временного скрипта (для local_machine):**
```bash
#!/bin/bash
set -e
echo "📦 Committing changes..."
git add -A
echo "💾 Creating commit..."
git commit -m "docs: описание изменений"
echo "🚀 Pushing to Gitea..."
git push origin main
echo "✅ Done!"
```

**Требования к скрипту:**
- ✅ Должен быть исполняемым (`chmod +x`)
- ✅ Должен содержать полное сообщение коммита с описанием изменений
- ✅ Для local_machine: `git push origin main` (или SSH remote, см. выше)
- ✅ Должен быть в папке `temp/` для временных скриптов
- ✅ Должен иметь понятные echo сообщения для пользователя

**Использование:**
```bash
# Выполнить скрипт
bash temp/commit-and-push.sh
```

**Очистка:**
После успешного выполнения скрипт можно удалить:
```bash
rm temp/commit-and-push.sh
```

**⚠️ ВАЖНО:**
- Скрипт должен быть создан агентом **ПЕРЕД** запросом пользователя на коммит
- Агент должен **ВСЕГДА** готовить скрипт, если терминал недоступен
- Скрипт должен содержать **ПОЛНОЕ** описание изменений в коммите

### Стандарт: без трейлеров в коммитах

В проекте **не используются** git-трейлеры в сообщениях коммитов (строки вида `Key: Value` в конце сообщения). Не добавлять:
- `Made-with: Cursor` и аналогичные метки инструмента
- `Co-authored-by:`, `Signed-off-by:` и т.п., если это не требуется явно политикой репозитория

Коммиты должны содержать только заголовок и при необходимости тело сообщения в читаемом формате.

### Проверки перед коммитом (опционально)

Если в проекте есть проверка типов или линтеры — их можно запускать перед коммитом по желанию. Для local_machine (документация и скрипты) обычно достаточно `git add` и `git commit`.

(В проекте local_machine нет frontend/backend — при необходимости добавьте свои проверки в скрипты.)

*Для других репо:* проверка прав на `.env` после деплоя и typecheck-скрипты — в документации того проекта.

### Метод 1: HTTPS с оптимизированными настройками

```bash
# Настройка git для больших push
git config http.postBuffer 524288000
git config http.lowSpeedLimit 0
git config http.lowSpeedTime 0
git config http.timeout 600

# Попытка push (для local_machine — ветка main)
git push origin main
```

**⚠️ На macOS:** HTTPS push часто не работает из-за отсутствия команды `timeout`. Скрипт автоматически пропускает HTTPS и использует bundle метод.

### Метод 2: Bundle метод (надежный fallback) {#резервный-способ-push-через-bundle}

Если HTTPS не работает, используется прямой доступ через файловую систему:

```bash
# Создать bundle (для local_machine — ветка main)
git bundle create /tmp/push.bundle origin/main..main

# Скопировать на сервер (hunab-prod — хост с доступом к Docker Gitea)
scp /tmp/push.bundle hunab-prod:/tmp/

# Применить в Gitea (репо local_machine)
ssh hunab-prod "docker cp /tmp/push.bundle gitea:/tmp/push.bundle && \
docker exec gitea sh -c 'cd /data/git/repositories/hunabgit/local_machine.git && \
git bundle unbundle /tmp/push.bundle && \
git update-ref refs/heads/main \$(git bundle list-heads /tmp/push.bundle | grep main | cut -d\" \" -f1) && \
rm /tmp/push.bundle' && rm /tmp/push.bundle"
```

**✅ Статус:** Bundle метод всегда работает и обходит все проблемы с таймаутами.

### Сравнение методов

| Метод | Скорость | Надежность | Сложность | Платформа |
|-------|----------|------------|-----------|-----------|
| **SSH** | ⚡⚡⚡ Быстро | ✅✅✅ Высокая | ✅ Просто | Все |
| **HTTPS (оптимизированный)** | ⚡⚡ Средне | ✅✅ Средняя | ✅ Просто | Linux |
| **Bundle (прямой доступ)** | ⚡ Медленно | ✅✅✅ Всегда работает | ⚠️ Сложнее | Все |

**Рекомендация:** Используйте автоматический скрипт - он выберет лучший метод автоматически.

### Миграция репозиториев

**Для регулярных push в local_machine:** `git push origin main` или SSH (см. раздел «Для репозитория local_machine» выше). В других репо может быть скрипт `git-push-gitea.sh`.

#### Шаг 1: Создание bundle из локального репозитория

```bash
cd /Users/eternal/code/local_machine
git bundle create /tmp/local_machine-migration.bundle --all
```

#### Шаг 2: Копирование bundle на сервер

```bash
scp /tmp/local_machine-migration.bundle hunab-prod:/tmp/local_machine-migration.bundle
```

#### Шаг 3: Распаковка bundle на сервере

```bash
ssh hunab-prod "cd /tmp && git clone --mirror local_machine-migration.bundle local_machine-mirror.git"
```

#### Шаг 4: Копирование в директорию Gitea

```bash
ssh hunab-prod "
  docker exec gitea mkdir -p /data/git/repositories/hunabgit/
  docker cp /tmp/local_machine-mirror.git gitea:/data/git/repositories/hunabgit/local_machine.git
  docker exec gitea chown -R git:git /data/git/repositories/hunabgit/local_machine.git
"
```

#### Шаг 5: Распаковка refs из packed-refs

**КРИТИЧЕСКИ ВАЖНО:** Gitea не видит ветки в `packed-refs`, нужно распаковать:

```bash
ssh hunab-prod "docker exec gitea sh -c 'cd /data/git/repositories/hunabgit/local_machine.git && cat packed-refs | grep \"refs/heads/\" | while read sha ref; do mkdir -p \$(dirname \$ref) && echo \$sha > \$ref; done'"
```

#### Шаг 6: Обновление базы данных

```bash
ssh hunab-prod "
  docker exec hunab-prod-postgres psql -U hunabgit -d hunabgit -c \"UPDATE repository SET is_empty = false WHERE name = 'local_machine';\"
  docker exec gitea /usr/local/bin/gitea admin regenerate hooks --config /data/gitea/conf/app.ini
  docker restart gitea
"
```

#### Шаг 7: Проверка

```bash
# Проверка веток через API
curl -s -H "Authorization: token YOUR_TOKEN" \
  https://gitea.hunab.app/api/v1/repos/hunabgit/local_machine/branches | jq '.[] | .name'

# Проверка файлов через API (ветка main)
curl -s -H "Authorization: token YOUR_TOKEN" \
  https://gitea.hunab.app/api/v1/repos/hunabgit/local_machine/git/trees/main?recursive=0 | jq '.tree[] | .path'

# Веб-интерфейс
# https://gitea.hunab.app/hunabgit/local_machine
```

**✅ Результат:** Репозиторий полностью мигрирован, все файлы доступны через веб-интерфейс и API.

### Аутентификация

#### Создание токена доступа

1. Откройте: https://gitea.hunab.app/user/settings/applications
2. Generate New Token
3. Название: `api-access`
4. Права: `write:repository`, `read:repository`, `read:user`
5. Скопируйте токен

#### Использование токена в git

```bash
# В локальном репозитории local_machine
cd /Users/eternal/code/local_machine
git remote set-url origin https://TOKEN@gitea.hunab.app/hunabgit/local_machine.git
```

---

## 🏷️ Создание тегов и Release (СТАНДАРТ)

### ⚠️ КРИТИЧЕСКИ ВАЖНО: Gitea не обновляет индекс тегов автоматически

**Проблема:** Если создать тег напрямую в git репозитории (`git tag -a v1.0.0`), Gitea **НЕ покажет его в веб-интерфейсе** автоматически. Gitea кэширует список тегов и обновляет его только при создании Release через веб-интерфейс или API.

### ✅ СТАНДАРТ: Создание тега через базу данных (РЕКОМЕНДУЕТСЯ)

**Используйте этот метод для автоматического создания тегов:**

```bash
# 1. Получить ID репозитория
REPO_ID=$(ssh hunab-prod "docker exec hunab-prod-postgres psql -U hunabgit -d hunabgit -t -c \"SELECT id FROM repository WHERE lower_name = 'local_machine';\" | tr -d ' '")

# 2. Создать Release в базе данных
ssh hunab-prod "docker exec hunab-prod-postgres psql -U hunabgit -d hunabgit -c \"
INSERT INTO release (
  repo_id, 
  publisher_id, 
  tag_name, 
  lower_tag_name, 
  target, 
  sha1, 
  title, 
  note, 
  is_draft, 
  is_prerelease, 
  is_tag, 
  created_unix
) VALUES (
  $REPO_ID,
  1,
  'v1.3.7-CRM',
  'v1.3.7-crm',
  'dev',
  '1542c302c36ad367281f8d9740e4d9f4a817ad23',
  'v1.3.7-CRM: Полная реализация CRM Contacts Page',
  'feat(CRM): Полная реализация CRM Contacts Page v1.3.7',
  false,
  false,
  true,
  EXTRACT(EPOCH FROM NOW())::bigint
) ON CONFLICT (repo_id, tag_name) DO NOTHING
RETURNING id, tag_name, title;
\""

# 3. Перезапустить Gitea для обновления индекса
ssh hunab-prod "docker restart gitea"
```

**Параметры:**
- `repo_id` - ID репозитория (получить через `SELECT id FROM repository WHERE lower_name = 'local_machine';`)
- `publisher_id` - ID пользователя (обычно 1 для первого пользователя)
- `tag_name` - Имя тега (например, `v1.3.7-CRM`)
- `lower_tag_name` - Имя тега в нижнем регистре (например, `v1.3.7-crm`)
- `target` - Ветка или commit (например, `dev` или `1542c302c36ad367281f8d9740e4d9f4a817ad23`)
- `sha1` - SHA коммита (40 символов)
- `title` - Заголовок Release
- `note` - Описание Release
- `is_tag` - `true` для тега, `false` для Release

### ✅ Альтернатива: Создание через веб-интерфейс

Если нужно создать тег вручную:

1. Откройте: https://gitea.hunab.app/hunabgit/local_machine/releases/new
2. В поле "Tag" введите имя тега (например, `v1.3.7-CRM`)
3. В поле "Target" выберите commit из списка или введите SHA
4. Заполните Title и Description
5. Нажмите "Publish Release"

**⚠️ Недостаток:** Требует ручного действия, не автоматизируется.

### ✅ Альтернатива: Создание через API (требует токен)

```bash
# 1. Получить токен доступа (один раз)
TOKEN=$(ssh hunab-prod "docker exec gitea /usr/local/bin/gitea admin user generate-access-token --username hunabgit --scopes 'write:repository' --name 'api-tag-creation' 2>&1 | grep -o '[a-z0-9]\{40\}' | head -1")

# 2. Создать Release через API
curl -X POST "https://gitea.hunab.app/api/v1/repos/hunabgit/local_machine/releases" \
  -H "Authorization: token $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "tag_name": "v1.3.7-CRM",
    "target_commitish": "1542c302c36ad367281f8d9740e4d9f4a817ad23",
    "name": "v1.3.7-CRM: Полная реализация CRM Contacts Page",
    "body": "feat(CRM): Полная реализация CRM Contacts Page v1.3.7",
    "draft": false,
    "prerelease": false
  }'
```

**⚠️ Недостаток:** Требует токен доступа, который нужно создать заранее.

### ❌ НЕ РАБОТАЕТ: Создание тега напрямую в git

```bash
# ❌ НЕ РАБОТАЕТ - Gitea не увидит тег в веб-интерфейсе
git tag -a v1.3.7-CRM -m "Message"
git push origin v1.3.7-CRM  # Даже после push тег не появится в веб-интерфейсе
```

**Почему не работает:**
- Gitea кэширует список тегов в базе данных
- Создание тега напрямую в git не обновляет таблицу `release`
- Gitea не сканирует репозиторий автоматически для поиска новых тегов

### 📋 Чеклист создания тега

- [ ] Получить ID репозитория из базы данных
- [ ] Получить SHA коммита для тега
- [ ] Создать Release в таблице `release` базы данных
- [ ] Перезапустить Gitea для обновления индекса
- [ ] Проверить что тег появился в веб-интерфейсе: https://gitea.hunab.app/hunabgit/local_machine/releases

### 🔧 Скрипт для автоматического создания тега

**Готовый скрипт:** `scripts/deployment/gitea/create-gitea-tag.sh`

**🇷🇺 ДЛЯ РФ:** Используйте прокси-версию для ускорения в 5-10 раз:
- `scripts/deployment/gitea/create-gitea-tag.sh` - обычный скрипт
- `scripts/deployment/gitea/create-gitea-tag-via-proxy.sh` - **🇷🇺 ДЛЯ РФ** - через прокси

**Использование:**
```bash
# Обычный скрипт
bash scripts/deployment/gitea/create-gitea-tag.sh \
  <tag-name> \
  <commit-sha> \
  <title> \
  [description] \
  [branch]

# 🇷🇺 ДЛЯ РФ: Через прокси (рекомендуется из РФ)
bash scripts/deployment/gitea/create-gitea-tag-via-proxy.sh \
  <tag-name> \
  <commit-sha> \
  <title> \
  [description] \
  [branch]
```

**Пример:**
```bash
bash scripts/deployment/gitea/create-gitea-tag.sh \
  v1.3.7-CRM \
  1542c302c36ad367281f8d9740e4d9f4a817ad23 \
  "v1.3.7-CRM: Полная реализация CRM Contacts Page" \
  "feat(CRM): Полная реализация CRM Contacts Page v1.3.7" \
  dev
```

**Что делает скрипт:**
1. ✅ Валидирует параметры (формат тега, SHA коммита)
2. ✅ Получает ID репозитория из базы данных
3. ✅ Создает Release в таблице `release`
4. ✅ Перезапускает Gitea для обновления индекса
5. ✅ Выводит ссылку для проверки

**Особенности:**
- Автоматически обрабатывает конфликты (обновляет существующий тег)
- Экранирует специальные символы в SQL
- Проверяет существование репозитория
- Выводит понятные сообщения об ошибках

---

## 📊 Мониторинг

### Проверка статуса

```bash
# Контейнер
ssh hunab-prod "docker ps | grep gitea"

# Логи
ssh hunab-prod "docker logs gitea --tail 50"

# Ресурсы
ssh hunab-prod "docker stats gitea --no-stream"

# API
curl https://gitea.hunab.app/api/v1/version

# SSL
openssl s_client -connect gitea.hunab.app:443 -servername gitea.hunab.app </dev/null 2>&1 | grep -E 'Protocol|Cipher'
```

### Проверка репозитория

```bash
# Ветки
ssh hunab-prod "docker exec gitea git --git-dir=/data/git/repositories/hunabgit/local_machine.git branch -a | wc -l"

# Размер
ssh hunab-prod "docker exec gitea du -sh /data/git/repositories/hunabgit/local_machine.git"

# Последний коммит
ssh hunab-prod "docker exec gitea git --git-dir=/data/git/repositories/hunabgit/local_machine.git log -1 --pretty=format:'%H %s'"
```

---

## 🔧 Troubleshooting

Для детальной диагностики проблем см. **[GITEA_TROUBLESHOOTING.md](./GITEA_TROUBLESHOOTING.md)** - Полное руководство по решению проблем.

Для диагностики зеркального сервера см. **[GITEA_MIRROR_TROUBLESHOOTING.md](./GITEA_MIRROR_TROUBLESHOOTING.md)**.

### Gitea не запускается

```bash
# Проверка логов
ssh hunab-prod "docker logs gitea --tail 100"

# Проверка портов
ssh hunab-prod "ss -tuln | grep -E '3000|2223'"

# Перезапуск
ssh hunab-prod "docker restart gitea"
```

### Ветки не видны в веб-интерфейсе

**Причина:** Refs в `packed-refs`, Gitea их не читает

**Решение:** Распаковать refs (см. Шаг 5 миграции)

**✅ Статус:** Проблема решена, все ветки видны в веб-интерфейсе

### Git hooks кажутся сломанными

**Проблема:** Gitea показывает предупреждение "Git hooks of this repository seem to be broken"

**Причина:** После прямой миграции репозитория hooks могут быть не синхронизированы с Gitea

**Решение:** Регенерировать hooks для всех репозиториев:

```bash
ssh hunab-prod "docker exec gitea /usr/local/bin/gitea admin regenerate hooks --config /data/gitea/conf/app.ini"
```

**Проверка:**
```bash
# Проверка прав на hooks
ssh hunab-prod "docker exec gitea ls -la /data/git/repositories/hunabgit/local_machine.git/hooks/pre-receive"

# Проверка содержимого hooks
ssh hunab-prod "docker exec gitea cat /data/git/repositories/hunabgit/local_machine.git/hooks/pre-receive.d/gitea"
```

**Дополнительно:** Если проблема сохраняется, проверьте:
1. Файловая система поддерживает выполнение (`chmod +x` работает)
2. Файловая система не смонтирована с `noexec`
3. Docker версия >= 20.10.6 (у нас 28.0.1 ✅)

**См. документацию Gitea:** https://docs.gitea.com/help/faq#push-hook--webhook--actions-arent-running

**✅ Статус:** Решено через `gitea admin regenerate hooks`

### Push через HTTPS зависает

**Проблема:** Git получает refs (GET запрос успешен), но POST запрос с данными не отправляется или соединение обрывается.

**Причина:** 
- Известная проблема с HTTP/2 и большими репозиториями в Gitea
- Проблемы с keepalive соединениями через reverse proxy
- Ограничения curl/git на стороне клиента (особенно на macOS)

**Решение:** 
1. ✅ **Используйте универсальный скрипт:** `bash scripts/deployment/gitea/git-push-gitea.sh [branch]`
2. ✅ Скрипт автоматически выбирает лучший метод (SSH > HTTPS > Bundle)
3. ✅ На macOS автоматически использует bundle метод (обходит проблемы с HTTPS)

**✅ Статус:** Систематически решено через универсальный скрипт (2026-01-02)

### SSL сертификат не обновляется

```bash
# Проверка автообновления
ssh hunab-prod "systemctl status certbot.timer"

# Ручное обновление
ssh hunab-prod "sudo certbot renew --dry-run"

# Копирование новых сертификатов
ssh hunab-prod "sudo cp /etc/letsencrypt/live/gitea.hunab.app/*.pem /opt/app/ssl/ && docker restart hunab-prod-nginx"
```

---

## 🔒 Безопасность

### Рекомендации

1. ✅ HTTPS через Let's Encrypt (настроено)
2. ✅ Modern SSL/TLS protocols (TLSv1.2, TLSv1.3)
3. ✅ Security headers (HSTS, X-Frame-Options, etc.)
4. ⚠️ Firewall для ограничения доступа (рекомендуется)
5. ✅ SSH ключи для git операций
6. ✅ Регулярные бэкапы данных

### Бэкапы

```bash
# Полный бэкап Gitea
ssh hunab-prod "
  tar -czf /opt/gitea-backup-$(date +%Y%m%d).tar.gz /opt/gitea
"

# Бэкап только репозиториев
ssh hunab-prod "
  tar -czf /opt/gitea-repos-backup-$(date +%Y%m%d).tar.gz /opt/gitea/git/repositories
"

# Бэкап базы данных
ssh hunab-prod "
  docker exec hunab-prod-postgres pg_dump -U hunabgit hunabgit > /opt/gitea-db-backup-$(date +%Y%m%d).sql
"
```

---

## 📈 Итоги и выводы

### Что было достигнуто

1. ✅ **Gitea развернут на production** (209.38.32.21)
2. ✅ **PostgreSQL настроен** (отдельная БД hunabgit)
3. ✅ **SSL/TLS настроен** (Let's Encrypt, A rating)
4. ✅ **Nginx reverse proxy** (с оптимизацией)
5. ✅ **Репозиторий local_machine** (и при необходимости другие репо) доступен на Gitea
6. ✅ **DNS настроен** (gitea.hunab.app → 209.38.32.21)
7. ✅ **Веб-интерфейс работает** (файлы отображаются корректно)
8. ✅ **API полностью функционален** (все endpoints работают)

### Ключевые проблемы и решения

#### Проблема 1: MySQL vs PostgreSQL
- **Проблема:** Gitea пытался подключиться к несуществующему MySQL
- **Решение:** Настроили PostgreSQL с отдельной БД

#### Проблема 2: SSL сертификат для поддомена
- **Проблема:** Существующий сертификат был для hunab.app, не gitea.hunab.app
- **Решение:** Получили новый сертификат через certbot --standalone

#### Проблема 3: Push через HTTPS зависает
- **Проблема:** Git получает refs, но POST запрос с данными не отправляется или соединение обрывается
- **Причина:** Известная проблема с HTTP/2 и большими репозиториями в Gitea, проблемы с keepalive соединениями
- **Попытки:** Увеличение таймаутов Nginx до 600s, отключение HTTP/2, оптимизация Connection headers
- **Решение:** Систематическое решение через универсальный скрипт `git-push-gitea.sh` (2026-01-02)
  - Автоматический выбор метода: SSH > HTTPS > Bundle
  - На macOS автоматически использует bundle метод
  - Всегда работает, обходит все проблемы с таймаутами

#### Проблема 4: Gitea не видит ветки
- **Проблема:** Refs в packed-refs, Gitea их не индексирует
- **Решение:** Распаковали refs в refs/heads/ структуру

#### Проблема 5: GitHub требует аутентификацию
- **Проблема:** git clone --mirror с сервера не работает без токена
- **Решение:** Использовали bundle, созданный локально

### Lessons Learned

1. **Прямое копирование эффективнее push** для больших репозиториев
2. **packed-refs не поддерживается Gitea** — нужна распаковка
3. **certbot --standalone** проще для новых сертификатов
4. **Docker network** критичен для PostgreSQL подключения
5. **Bundle** — универсальный способ переноса репозиториев

### Производительность

- **Обычный git push через HTTPS:** ❌ Зависает (POST запрос не отправляется или соединение обрывается)
- **Универсальный скрипт (git-push-gitea.sh):** ✅ Автоматически выбирает лучший метод
  - SSH: ⚡⚡⚡ Быстро (если доступен)
  - HTTPS: ⚡⚡ Средне (только на Linux)
  - Bundle: ⚡ Медленно, но всегда работает (особенно на macOS)
- **🇷🇺 ДЛЯ РФ: Прокси-версия (git-push-gitea-via-proxy.sh):** ✅ Ускорение в 5-10 раз
  - Работает через российский прокси-сервер (149.154.64.19)
  - Стабильное соединение к Digital Ocean
  - Избежание таймаутов при работе из РФ
  - Bundle метод через прокси: ⚡⚡ Быстро и надежно
  - ✅ **Мгновенная верификация** - обновление tracking ref без сетевых запросов (2026-02-27)
- **Прямое копирование через bundle:** ✅ ~1-2 минуты для инкрементальных обновлений
- **Полная миграция:** ✅ ~5-10 минут (включая распаковку refs)
- **Размер репозитория:** 556.8M (23 ветки, история с 2020 года)
- **Текущий статус:** ✅ Все файлы доступны, веб-интерфейс работает корректно
- **Скрипт для push:** ✅ `scripts/deployment/gitea/git-push-gitea.sh` - универсальное решение (2026-01-02)

### Следующие шаги (опционально)

1. **Настроить CI/CD** в Gitea (Gitea Actions)
2. **Настроить зеркалирование** GitHub ↔ Gitea (для резервирования)
3. **Настроить SSH ключи** для бесшовной работы
4. **Добавить DNS CAA запись** для дополнительной безопасности
5. **Настроить автоматические бэкапы** (cron job)
6. **Мигрировать deployment скрипты** на использование Gitea

---

## 📚 Полезные команды

**Для local_machine:**
```bash
cd /Users/eternal/code/local_machine
git push origin main
# или SSH (если HTTPS таймаутит): git remote set-url origin ssh://git@gitea.hunab.app:2223/hunabgit/local_machine.git
```

**Для админов сервера (Gitea Admin):**
```bash
docker exec gitea /usr/local/bin/gitea admin --help
docker exec gitea /usr/local/bin/gitea admin regenerate hooks

# Git операции в Gitea (репо local_machine)
docker exec gitea git --git-dir=/data/git/repositories/hunabgit/local_machine.git branch -a
docker exec gitea git --git-dir=/data/git/repositories/hunabgit/local_machine.git log --oneline -10

# PostgreSQL операции
docker exec hunab-prod-postgres psql -U hunabgit -d hunabgit -c "SELECT * FROM repository;"

# Проверка конфигурации базы данных
docker exec gitea cat /data/gitea/conf/app.ini | grep -A 10 '\[database\]'
# Должно быть: DB_TYPE = postgres, HOST = <postgres-container>:5432

# SSL проверка
openssl s_client -connect gitea.hunab.app:443 -servername gitea.hunab.app
curl -vI https://gitea.hunab.app 2>&1 | grep -E 'SSL|TLS'

# Nginx
docker exec hunab-prod-nginx nginx -t
docker logs hunab-prod-nginx --tail 50

# Проверка логов Gitea при push
ssh hunab-prod "docker logs gitea --tail 50 | grep -E '(receive-pack|upload-pack|error)'"
```

---

## 🔄 Зеркалирование

Для полного руководства по зеркалированию см. **[GITEA_MIRRORING_GUIDE.md](./GITEA_MIRRORING_GUIDE.md)**.

### Миграция Gitea на зеркальный сервер

**📋 План миграции:** См. **[GITEA_MIGRATION_TO_MIRROR_SERVER.md](./GITEA_MIGRATION_TO_MIRROR_SERVER.md)** - Детальный план миграции Gitea с production сервера (209.38.32.21) на зеркальный сервер (206.189.35.205).

**Причины миграции:**
- Освобождение дискового пространства на production сервере (96% заполнено)
- Изоляция рисков - проблемы Gitea не влияют на production
- Улучшение производительности Git операций
- Масштабируемость

### Зеркальный сервер (206.189.35.205)

Для резервирования и высокой доступности настроено зеркалирование всех репозиториев на внешний сервер.

**Документация:**
- [Gitea Mirroring Guide](./GITEA_MIRRORING_GUIDE.md) - Полный план зеркалирования
- [Gitea Migration to Mirror Server](./GITEA_MIGRATION_TO_MIRROR_SERVER.md) - **План миграции Gitea на зеркальный сервер (206.189.35.205)**
- [Gitea Mirror Troubleshooting](./GITEA_MIRROR_TROUBLESHOOTING.md) - Диагностика зеркального сервера
- [Gitea Legal — сервер 206.189.35.205](./GITEA_LEGAL_206_SERVER_GUIDE.md) - Стандарт для репозитория legal

**Быстрый старт:**
```bash
# Развертывание зеркального сервера
bash scripts/deployment/gitea/deploy-gitea-mirror-server.sh

# Миграция всех репозиториев
bash scripts/deployment/mirror-all-repos-to-external.sh
```

**Архитектура:**
- **Основной сервер (209.38.32.21):** Primary Gitea, все репозитории
- **Зеркальный сервер (206.189.35.205):** Mirror Gitea, автоматическая синхронизация

---

## 🔗 Ссылки

- **Веб-интерфейс:** https://gitea.hunab.app
- **API:** https://gitea.hunab.app/api/v1/
- **Репозиторий local_machine:** https://gitea.hunab.app/hunabgit/local_machine
- **Быстрый старт local_machine:** [LOCAL_MACHINE_GITEA_SETUP.md](./LOCAL_MACHINE_GITEA_SETUP.md)
- **Документация Gitea:** https://docs.gitea.io/
- **SSL Report:** https://www.ssllabs.com/ssltest/analyze.html?d=gitea.hunab.app
- **Зеркалирование:** [Gitea Mirroring Guide](./GITEA_MIRRORING_GUIDE.md) (если есть)
- **Миграция на зеркальный сервер:** [Gitea Migration Plan](./GITEA_MIGRATION_TO_MIRROR_SERVER.md) (если есть)

---

## ✅ Текущий статус

### Репозиторий local_machine
- **URL:** https://gitea.hunab.app/hunabgit/local_machine
- **Ветка по умолчанию:** main
- **Пуш:** `git push origin main` или SSH (порт 2223) — см. [LOCAL_MACHINE_GITEA_SETUP.md](./LOCAL_MACHINE_GITEA_SETUP.md)

### Система (сервер Gitea)
- ✅ Gitea контейнер на 209.38.32.21
- ✅ PostgreSQL, SSL/TLS, Nginx
- ✅ DNS: gitea.hunab.app → 209.38.32.21

**Последнее обновление:** 2026-03-14  
**Версия документа:** 2.0 (адаптация под проект local_machine)  
**Изменения:**
- Гайд переориентирован на репозиторий local_machine; примеры и команды приведены к этому проекту
- Добавлена ссылка на LOCAL_MACHINE_GITEA_SETUP.md для быстрого старта

---

## 🧹 Очистка истории Git от build-артефактов

### 🎯 Когда нужна очистка vs простое удаление

**99% случаев:** Файлы уже в `.gitignore`, но Git их отслеживает (закоммичены до добавления в `.gitignore`)

**Решение:** ✅ **Простое удаление из tracking** (см. ниже) — БЕЗ переписывания истории!

*Пример ниже — для репо с frontend (build-артефакты). В local_machine: убедиться, что путь в .gitignore, затем `git rm --cached -r путь/` и коммит.*

**1% случаев:** Нужно удалить файлы из ВСЕЙ истории (например, случайно закоммитили секреты)

**Решение:** ⚠️ **Очистка истории** (см. раздел "Когда действительно нужна очистка истории")

---

## ✅ ПРАВИЛЬНЫЙ ПОДХОД: Простое удаление из tracking

**Когда использовать:** Файлы уже в `.gitignore`, но Git их отслеживает.

**Пример проблемы:**
- Файлы в `.gitignore`: `frontend/dist-*`
- Git все еще отслеживает: 4046 файлов из `dist-*` папок
- `git status` показывает: `D frontend/dist-cache-busting/404.html` и т.д.

### ✅ Решение (ПРАВИЛЬНО):

```bash
# 1. Убедиться что файлы в .gitignore
cat frontend/.gitignore | grep dist-

# 2. Удалить из tracking (файлы останутся в рабочей директории, если есть)
git rm --cached -r frontend/dist-cache-busting/ \
                    frontend/dist-fast-no-image/ \
                    frontend/dist-via-proxy/ \
                    frontend/dist-zero-downtime/ 2>/dev/null || \
git add -u -- frontend/dist-*/  # Альтернатива если файлы уже удалены из рабочей директории

# 3. Проверить что файлы в staging для удаления
git status --short | grep "^D " | wc -l
# Должно показать количество удаляемых файлов

# 4. Закоммитить удаление
git commit -m "chore(frontend): удаление dist-* build-артефактов из Git tracking

- Удалены файлы из frontend/dist-cache-busting/, dist-fast-no-image/, dist-via-proxy/, dist-zero-downtime/
- Файлы были в .gitignore, но отслеживались Git (закоммичены до добавления в .gitignore)
- Build-артефакты генерируются при деплое и не должны храниться в репозитории"

# 5. Push в Gitea (для local_machine: git push origin main или SSH)
bash scripts/deployment/gitea/git-push-gitea-via-proxy.sh dev  # в других репо; для local_machine: git push origin main

# 6. Проверка
git ls-files | grep -E "^frontend/dist-(zero-downtime|fast-no-image|via-proxy|cache-busting)/" | wc -l
# Должно быть: 0
```

**✅ Результат:**
- Файлы удалены из tracking (больше не отслеживаются Git)
- История НЕ переписана (SHA коммитов не изменились)
- Все коммиты сохранены
- Работает быстро и безопасно

**⚠️ ВАЖНО:** После этого файлы останутся в истории старых коммитов, но новые коммиты их не будут содержать. Это нормально для build-артефактов.

---

## ❌ КАК НЕ НАДО: git-filter-repo с --invert-paths

### ❌ КРИТИЧЕСКАЯ ОШИБКА (НЕ ДЕЛАТЬ ТАК!):

```bash
# ❌ НЕПРАВИЛЬНО - УНИЧТОЖАЕТ ВСЕ КОММИТЫ!
git filter-repo \
  --path frontend/dist-zero-downtime/ \
  --path frontend/dist-fast-no-image/ \
  --path frontend/dist-via-proxy/ \
  --path frontend/dist-cache-busting/ \
  --invert-paths \
  --force
```

**Что происходит:**
- ❌ `git-filter-repo` с `--invert-paths` **УНИЧТОЖАЕТ ВСЕ КОММИТЫ**
- ❌ Все tree objects становятся пустыми (0 файлов в коммитах)
- ❌ Репозиторий полностью сломан
- ❌ Нужно восстанавливать из бэкапа

**Почему это происходит:**
- `--invert-paths` означает "сохранить все, кроме указанных путей"
- Но если указанные пути были в КАЖДОМ коммите, то все коммиты становятся пустыми
- Git не может создать коммит без файлов

**Реальный пример ошибки (2026-02-27):**
```
# После git-filter-repo:
git ls-tree -r HEAD --name-only | wc -l
# Результат: 0 (ВСЕ КОММИТЫ ПУСТЫЕ!)

git log --oneline -5
# Коммиты есть, но в них НЕТ ФАЙЛОВ
```

**✅ Решение после ошибки:**
```bash
# Восстановить из бэкапа
rm -rf .git
mv .git.backup-20260227-171445 .git

# Использовать ПРАВИЛЬНЫЙ подход (см. выше)
git rm --cached -r frontend/dist-*/ && git commit -m "..."
```

---

## ⚠️ Когда действительно нужна очистка истории

**Редкие случаи, когда нужна очистка истории:**

1. **Случайно закоммитили секреты** (пароли, API ключи, токены)
   - ⚠️ Критично: секреты в истории = уязвимость безопасности
   - ✅ Нужна полная очистка истории

2. **Огромные бинарные файлы** (видео, большие изображения, базы данных)
   - ⚠️ Размер репозитория > 2GB из-за истории
   - ✅ Нужна очистка для уменьшения размера

3. **Юридические требования** (удаление файлов по запросу)
   - ⚠️ Требуется полное удаление из истории
   - ✅ Нужна очистка истории

**⚠️ ВАЖНО:** Для build-артефактов (`dist/`, `build/`) очистка истории **НЕ НУЖНА** - достаточно простого удаления из tracking!

---

## 🧹 Очистка истории от файлов с секретами (docs)

**Когда применять:** после замены реальных секретов на плейсхолдеры в документации (см. [SECRETS_AND_FILE_PERMISSIONS_POLICY.md](../../security/SECRETS_AND_FILE_PERMISSIONS_POLICY.md)) нужно удалить из истории Git старые версии этих файлов, чтобы секреты нельзя было извлечь из старых коммитов.

**Гарантированно безопасный порядок (обязательно):**

1. **Бэкап репозитория (ОБЯЗАТЕЛЬНО)** — без бэкапа не приступать.
2. Запуск скрипта, который делает бэкап сам и выполняет очистку по списку путей.
3. Проверки целостности (в скрипте).
4. Применение очищенной версии на Gitea только после бэкапа репо на сервере.

### Скрипт очистки (бэкап + filter-repo + проверки)

Используется готовый скрипт, который:

- Создаёт резервную копию (`git clone --mirror`) в каталог рядом с репо.
- Клонирует репо в bare, удаляет из истории пути из списка (`git filter-repo --paths-from-file ... --invert-paths`).
- Проверяет целостность (`git ls-tree -r HEAD | wc -l` > 0, `git fsck`).
- Восстанавливает в текущем tip почищенные версии файлов (один коммит с плейсхолдерами).

**Запуск (из корня репозитория):**

```bash
# Установить git-filter-repo (один раз)
# macOS:
brew install git-filter-repo
# Linux:
# pip3 install git-filter-repo

# Запуск (бэкап создаётся скриптом автоматически)
bash scripts/maintenance/git-history-cleanup-secrets.sh /path/to/local_machine
```

**Файлы:**

- Список путей (файлы, которые удаляются из истории): `scripts/maintenance/git-history-cleanup-secrets-paths.txt` — соответствует таблице в [SECRETS_AND_FILE_PERMISSIONS_POLICY.md](../../security/SECRETS_AND_FILE_PERMISSIONS_POLICY.md) (очистка 2026-03-03).
- Скрипт: `scripts/maintenance/git-history-cleanup-secrets.sh`.

**После скрипта:** выполнить шаги из раздела ниже «Правильная очистка истории»: создание bundle из очищенного bare-репо, загрузка на сервер, **бэкап Gitea-репо на сервере**, замена objects/refs, перезапуск Gitea, обновление локального репо.

**Критично:** на production-сервере перед заменой репозитория в Gitea обязательно создать бэкап:

```bash
GITEA_REPO="/data/git/repositories/hunabgit/local_machine.git"
ssh hunab-prod "docker exec gitea cp -r $GITEA_REPO ${GITEA_REPO}.backup-$(date +%Y%m%d)"
```

---

## ✅ Правильная очистка истории (если действительно нужна)

**⚠️ КРИТИЧЕСКИ ВАЖНО:** Очистка истории переписывает все коммиты (SHA изменятся), но содержимое сохраняется.

### Шаг 1: Резервная копия (ОБЯЗАТЕЛЬНО!)

```bash
# Создать полную резервную копию репозитория
cd /path/to/repo
git clone --mirror . ../repo-backup-$(date +%Y%m%d-%H%M%S).git

# Проверить размер
du -sh ../repo-backup-*.git
```

**✅ Результат:** Резервная копия с полной историей (можно восстановить в любой момент)

### Шаг 2: Установка git-filter-repo

```bash
# macOS
brew install git-filter-repo

# Linux
pip3 install git-filter-repo

# Проверка
git filter-repo --version
```

### Шаг 3: Удаление файлов из истории (ПРАВИЛЬНЫЙ СПОСОБ)

**⚠️ КРИТИЧЕСКИ ВАЖНО:** Используйте `--path` БЕЗ `--invert-paths` для удаления конкретных файлов!

```bash
# Создать bare clone для безопасной работы
cd /tmp
git clone --mirror /path/to/repo repo-clean.git

# ✅ ПРАВИЛЬНО: Удалить конкретные файлы/папки
cd repo-clean.git
git filter-repo \
  --path frontend/dist-zero-downtime/ \
  --path frontend/dist-fast-no-image/ \
  --path frontend/dist-via-proxy/ \
  --path frontend/dist-cache-busting/ \
  --path-glob '*.secret' \
  --path-glob '*.key' \
  --invert-paths \
  --force

# ❌ НЕПРАВИЛЬНО: --invert-paths может уничтожить все коммиты!
# Используйте ТОЛЬКО если уверены что файлы НЕ в каждом коммите

# Очистить reflog и неиспользуемые объекты
git reflog expire --expire=now --all
git gc --prune=now --aggressive
```

**⚠️ КРИТИЧЕСКОЕ ПРЕДУПРЕЖДЕНИЕ:**
- `--invert-paths` **ОПАСЕН** - может уничтожить все коммиты если указанные пути были в каждом коммите
- **Лучше использовать:** `--path` для удаления конкретных файлов БЕЗ `--invert-paths`
- **Проверяйте результат:** `git ls-tree -r HEAD --name-only | wc -l` должно быть > 0!

### Шаг 4: Проверка целостности (КРИТИЧЕСКИ ВАЖНО!)

```bash
cd repo-clean.git

# ✅ КРИТИЧЕСКАЯ ПРОВЕРКА: Файлы в коммитах
git ls-tree -r HEAD --name-only | wc -l
# ДОЛЖНО БЫТЬ > 0! Если 0 - репозиторий сломан, восстанавливайте из бэкапа!

# Проверить размер
du -sh .
# Должно быть меньше оригинала

# Проверить количество коммитов
git log --oneline --all | wc -l
# Должно быть идентично оригиналу

# Проверить что файлы удалены
git rev-list --all --objects | grep -E "(dist-zero-downtime|dist-fast-no-image)" | wc -l
# Должно быть: 0

# Проверить целостность
git fsck --full
# Не должно быть ошибок

# Проверить важные ветки
git branch -a | grep -E "(dev|main|staging)"
# Все важные ветки должны быть сохранены
```

**✅ Гарантии:**
- ✅ Все коммиты сохранены (количество идентично)
- ✅ **Файлы в коммитах > 0** (критично!)
- ✅ Все сообщения коммитов идентичны
- ✅ Все авторы и даты сохранены
- ✅ Все важные ветки сохранены
- ✅ Все теги сохранены
- ✅ Файлы удалены из истории

**⚠️ КРИТИЧЕСКИ ВАЖНО:** Очистка истории переписывает все коммиты (SHA изменятся), но содержимое сохраняется.

#### Шаг 1: Резервная копия (ОБЯЗАТЕЛЬНО!)

```bash
# Создать полную резервную копию репозитория
cd /path/to/repo
git clone --mirror . ../repo-backup-$(date +%Y%m%d-%H%M%S).git

# Проверить размер
du -sh ../repo-backup-*.git
```

**✅ Результат:** Резервная копия с полной историей (можно восстановить в любой момент)

#### Шаг 2: Установка git-filter-repo

```bash
# macOS
brew install git-filter-repo

# Linux
pip3 install git-filter-repo

# Проверка
git filter-repo --version
```

#### Шаг 3: Удаление файлов из истории

```bash
# Создать bare clone для безопасной работы
cd /tmp
git clone --mirror /path/to/repo repo-clean.git

# Удалить папки из истории
cd repo-clean.git
git filter-repo \
  --path frontend/dist-zero-downtime/ \
  --path frontend/dist-fast-no-image/ \
  --path frontend/dist-via-proxy/ \
  --path frontend/dist-cache-busting/ \
  --invert-paths \
  --force

# Очистить reflog и неиспользуемые объекты
git reflog expire --expire=now --all
git gc --prune=now --aggressive
```

**Параметры:**
- `--path` - путь к папке/файлу для удаления
- `--path-glob` - glob паттерн для удаления (например, `*.secret`)
- `--invert-paths` - ⚠️ **ОПАСНО!** Удалить указанные пути (остальное сохранить). Может уничтожить все коммиты!
- `--force` - принудительное выполнение

**✅ Результат:** Очищенный репозиторий без указанных файлов

**⚠️ КРИТИЧЕСКОЕ ПРЕДУПРЕЖДЕНИЕ:**
- `--invert-paths` **ОПАСЕН** - если указанные пути были в КАЖДОМ коммите, все коммиты станут пустыми!
- **Всегда проверяйте:** `git ls-tree -r HEAD --name-only | wc -l` должно быть > 0 после очистки!
- **Если результат = 0:** Репозиторий сломан, восстанавливайте из бэкапа!

#### Шаг 4: Проверка целостности (ОБЯЗАТЕЛЬНО!)

```bash
cd repo-clean.git

# Проверить размер
du -sh .
# Должно быть меньше оригинала

# Проверить количество коммитов
git log --oneline --all | wc -l
# Должно быть идентично оригиналу

# Проверить что файлы удалены
git rev-list --all --objects | grep -E "(dist-zero-downtime|dist-fast-no-image)" | wc -l
# Должно быть: 0

# Проверить целостность
git fsck --full
# Не должно быть ошибок

# Проверить важные ветки
git branch -a | grep -E "(dev|main|staging)"
# Все важные ветки должны быть сохранены
```

**✅ Гарантии:**
- ✅ Все коммиты сохранены (количество идентично)
- ✅ Все сообщения коммитов идентичны
- ✅ Все авторы и даты сохранены
- ✅ Все важные ветки сохранены
- ✅ Все теги сохранены
- ✅ Файлы удалены из истории

#### Шаг 5: Применение очищенной версии

**⚠️ ПРОБЛЕМА:** Прямой push в Gitea таймаутит из РФ, а скрипт через прокси не работает, потому что после замены `.git` нет remote tracking branches.

**✅ РЕШЕНИЕ: Bundle метод + прямое копирование на сервер**

```bash
# 1. Создать bundle из очищенного репозитория
cd /tmp/repo-clean.git
git bundle create /tmp/repo-clean.bundle --all

# 2. Загрузить bundle на production сервер
scp -i ~/.ssh/hunab_deploy_key /tmp/repo-clean.bundle hunab@209.38.32.21:/tmp/

# 3. Создать временный репозиторий из bundle на сервере
ssh hunab-prod "cd /tmp && git clone --mirror /tmp/repo-clean.bundle repo-clean.git"

# 4. Бэкап текущего Gitea репозитория
GITEA_REPO="/data/git/repositories/hunabgit/local_machine.git"
ssh hunab-prod "docker exec gitea cp -r $GITEA_REPO ${GITEA_REPO}.backup-$(date +%Y%m%d)"

# 5. Копировать очищенный репозиторий в контейнер
ssh hunab-prod "docker cp /tmp/repo-clean.git gitea:/tmp/repo-clean.git"

# 6. Заменить objects и refs в Gitea репозитории
ssh hunab-prod "docker exec gitea sh -c '
  cd $GITEA_REPO
  rm -rf objects packed-refs refs/heads refs/tags
  cp -r /tmp/repo-clean.git/objects .
  cp -r /tmp/repo-clean.git/packed-refs . 2>/dev/null || true
  cp -r /tmp/repo-clean.git/refs/heads refs/ 2>/dev/null || true
  cp -r /tmp/repo-clean.git/refs/tags refs/ 2>/dev/null || true
  echo \"ref: refs/heads/main\" > HEAD
  rm -rf refs/original filter-repo
  git reflog expire --expire=now --all 2>/dev/null
  git gc --prune=now 2>&1 | tail -5
  chown -R git:git .
'"

# 7. Перезапустить Gitea для обновления индексов
ssh hunab-prod "docker restart gitea && sleep 5"

# 8. Проверка
ssh hunab-prod "docker exec gitea sh -c 'cd $GITEA_REPO && \
  echo \"Размер: \$(du -sh .)\" && \
  echo \"Коммитов: \$(git log --oneline --all | wc -l)\" && \
  echo \"Файлов dist в истории: \$(git rev-list --all --objects | grep -cE \"dist-zero-downtime|dist-fast-no-image|dist-via-proxy|dist-cache-busting\" || echo 0)\" && \
  echo \"Веток: \$(git branch | wc -l)\" && \
  echo \"Тегов: \$(git tag | wc -l)\" && \
  git log --oneline -3 main'"
```

**✅ Результат:** Gitea репозиторий обновлен очищенной версией

#### Шаг 6: Обновление локального репозитория

```bash
# Заменить .git папку очищенной версией
cd /path/to/repo
rm -rf .git
cp -r /tmp/repo-clean.git .git

# Преобразовать bare в обычный репозиторий
git config core.bare false
git config core.worktree .

# Проверить статус
git status
git log --oneline -5
```

**⚠️ ВАЖНО:** После замены `.git` могут появиться артефакты в `git status` (untracked файлы из `.git`). Это не критично - репозиторий работает корректно.

### 📊 Результаты: Простое удаление из tracking (ПРАВИЛЬНЫЙ подход)

**До удаления:**
- Файлов отслеживается: 13,281
- Файлов dist-* в tracking: 4,046
- Коммитов: 6,218

**После удаления:**
- Файлов отслеживается: 13,281 - 4,046 = 9,235
- Файлов dist-* в tracking: 0 ✅
- Коммитов: 6,218 (все сохранены, SHA не изменились) ✅
- Веток: 27 (все важные сохранены) ✅
- Тегов: 162 (все сохранены) ✅
- Время выполнения: ~30 секунд ✅
- Риск: Минимальный (простой коммит) ✅

**⚠️ ВАЖНО:** Файлы останутся в истории старых коммитов, но новые коммиты их не будут содержать. Это нормально для build-артефактов.

### 📊 Результаты: Очистка истории (если действительно нужна)

**До очистки:**
- Размер: 968 MB
- Файлов в истории: 4,046 из `dist-*` папок
- Коммитов: 6,218

**После очистки (если все прошло правильно):**
- Размер: 391 MB (экономия 577 MB, 60%)
- Файлов в истории: 0 из `dist-*` папок
- Коммитов: 6,218 (все сохранены, но SHA изменились)
- Веток: 27 (все важные сохранены)
- Тегов: 162 (все сохранены)
- Время выполнения: ~30-60 минут (с бэкапом и проверками)
- Риск: Высокий (переписывание истории) ⚠️

**❌ Результаты после ОШИБКИ (git-filter-repo с --invert-paths):**
- Файлов в коммитах: 0 (ВСЕ КОММИТЫ ПУСТЫЕ!) ❌
- Репозиторий: СЛОМАН ❌
- Решение: Восстановление из бэкапа ✅

### 🛡️ Механизмы защиты

**Для простого удаления из tracking:**
1. ✅ **Проверка .gitignore:** Убедиться что файлы в `.gitignore`
2. ✅ **Проверка staging:** `git status --short | grep "^D "` показывает удаляемые файлы
3. ✅ **Проверка после коммита:** `git ls-files | grep dist- | wc -l` = 0

**Для очистки истории (если действительно нужна):**
1. ✅ **Резервная копия:** Полная копия оригинального репозитория (ОБЯЗАТЕЛЬНО!)
2. ✅ **Бэкап на сервере:** Копия Gitea репозитория перед заменой
3. ✅ **КРИТИЧЕСКАЯ проверка:** `git ls-tree -r HEAD --name-only | wc -l` > 0 после очистки!
4. ✅ **Проверка целостности:** `git fsck --full` перед применением
5. ✅ **Валидация:** Проверка количества коммитов, веток, тегов, файлов в коммитах

### ⚠️ Критические ошибки и решения

#### ❌ Ошибка 0: git-filter-repo уничтожил все коммиты (КРИТИЧЕСКАЯ!)

**Проблема:** После `git-filter-repo --invert-paths` все коммиты стали пустыми (0 файлов).

**Симптомы:**
```bash
git ls-tree -r HEAD --name-only | wc -l
# Результат: 0 (ВСЕ КОММИТЫ ПУСТЫЕ!)

git log --oneline -5
# Коммиты есть, но в них НЕТ ФАЙЛОВ

git status
# Показывает ВСЕ файлы как untracked
```

**Причина:** `--invert-paths` удалил указанные пути из ВСЕХ коммитов. Если эти пути были в каждом коммите, все коммиты стали пустыми.

**✅ Решение:**
```bash
# 1. Восстановить из бэкапа
rm -rf .git
mv .git.backup-20260227-171445 .git  # Используйте ваш бэкап

# 2. Использовать ПРАВИЛЬНЫЙ подход (простое удаление из tracking)
git rm --cached -r frontend/dist-*/ && git commit -m "..."

# 3. НЕ использовать git-filter-repo для build-артефактов!
```

**Профилактика:**
- ✅ **ВСЕГДА** проверяйте `git ls-tree -r HEAD --name-only | wc -l` после git-filter-repo
- ✅ **ВСЕГДА** создавайте полный бэкап перед очисткой истории
- ✅ **НЕ используйте** `--invert-paths` для файлов, которые были в каждом коммите
- ✅ **Для build-артефактов** используйте простое удаление из tracking (см. "ПРАВИЛЬНЫЙ ПОДХОД" выше)

#### Ошибка 1: Прямой push таймаутит

**Проблема:** `git push --force` зависает с таймаутом из РФ.

**Решение:** Использовать bundle метод + прямое копирование на сервер (см. Шаг 5).

#### Ошибка 2: Скрипт через прокси не работает

**Проблема:** После замены `.git` нет remote tracking branches (`origin/dev` не существует).

**Решение:** Bundle метод обходит эту проблему, так как работает напрямую с файловой системой.

#### Ошибка 3: Файлы из `.git` в корне проекта

**Проблема:** После замены `.git` git status показывает untracked файлы (COMMIT_EDITMSG, HEAD, config и т.д.).

**Решение:** Это артефакты git status - файлы не существуют в корне, можно игнорировать. Репозиторий работает корректно.

#### Ошибка 4: Gitea не видит изменения

**Проблема:** После замены объектов Gitea не обновляет индекс.

**Решение:** 
1. Удалить `refs/original` и `filter-repo` из репозитория
2. Очистить reflog: `git reflog expire --expire=now --all`
3. Перезапустить Gitea: `docker restart gitea`

### 🔄 Восстановление из резервной копии

Если что-то пошло не так:

```bash
# Восстановить локальный репозиторий
cd /path/to/repo
rm -rf .git
git clone --mirror /path/to/backup/repo-backup-*.git .git
git config core.bare false
git config core.worktree .

# Восстановить Gitea репозиторий на сервере
ssh hunab-prod "docker exec gitea rm -rf $GITEA_REPO && \
  docker exec gitea cp -r ${GITEA_REPO}.backup-* $GITEA_REPO && \
  docker exec gitea chown -R git:git $GITEA_REPO && \
  docker restart gitea"
```

### 📋 Чеклист: Простое удаление из tracking (99% случаев)

**Для build-артефактов (`dist/`, `build/`) - используйте этот чеклист:**

- [ ] Проверено что файлы в `.gitignore`
- [ ] Удалены файлы из tracking: `git rm --cached -r frontend/dist-*/`
- [ ] Проверено что файлы в staging для удаления: `git status --short | grep "^D "`
- [ ] Создан коммит с описанием удаления
- [ ] Push в Gitea (local_machine: `git push origin main` или SSH)
- [ ] Проверено что файлы больше не отслеживаются: `git ls-files | grep dist- | wc -l` = 0
- [ ] Обновлен CHANGELOG с описанием операции

**✅ Результат:** Файлы удалены из tracking, история НЕ переписана, все работает быстро и безопасно.

---

### 📋 Чеклист: Очистка истории (1% случаев - только для секретов/юридических требований)

**⚠️ ВАЖНО:** Используйте ТОЛЬКО если действительно нужно удалить файлы из ВСЕЙ истории!

- [ ] Определено что простая очистка tracking НЕДОСТАТОЧНА (секреты, юридические требования)
- [ ] Создана резервная копия оригинального репозитория
- [ ] Установлен `git-filter-repo`
- [ ] Создан bare clone для безопасной работы
- [ ] Удалены файлы из истории через `git-filter-repo` (БЕЗ `--invert-paths` если возможно!)
- [ ] **КРИТИЧЕСКАЯ ПРОВЕРКА:** `git ls-tree -r HEAD --name-only | wc -l` > 0 (если 0 - репозиторий сломан!)
- [ ] Очищены reflog и неиспользуемые объекты
- [ ] Проверена целостность (коммиты, ветки, теги, файлы в коммитах > 0)
- [ ] Создан bundle из очищенного репозитория
- [ ] Загружен bundle на production сервер (в `/home/hunab/`, НЕ в `/tmp/` - tmpfs только 2GB!)
- [ ] Создан бэкап Gitea репозитория на сервере
- [ ] Заменены objects и refs в Gitea репозитории
- [ ] Удалены `refs/original` и `filter-repo`
- [ ] Перезапущен Gitea
- [ ] Проверен результат (размер, коммиты, файлы в коммитах > 0)
- [ ] Обновлен локальный репозиторий
- [ ] Обновлен CHANGELOG с описанием операции

### 📚 Связанные документы

- **[Deployment Changelog](../CHANGELOG.md)** - История изменений deployment (включает очистку истории)
- **[Git Filter Repo Documentation](https://github.com/newren/git-filter-repo)** - Официальная документация git-filter-repo

---

**Последнее обновление:** 2026-02-27  
**Версия документа:** 1.6  
**Статус:** ✅ Gitea полностью развернут и работает стабильно  
**Изменения:** 
- Добавлен резервный способ коммитов через временный скрипт (2026-02-16)
- Добавлен гайд по очистке истории Git от build-артефактов (2026-02-27)
- **КРИТИЧЕСКОЕ ОБНОВЛЕНИЕ (2026-02-27):** Добавлены четкие предупреждения о том, как НЕ надо делать очистку истории:
  - ❌ `git-filter-repo --invert-paths` УНИЧТОЖАЕТ все коммиты (реальный пример ошибки 2026-02-27)
  - ✅ Правильный подход: простое удаление из tracking (`git rm --cached`) для build-артефактов (99% случаев)
  - ✅ Очистка истории нужна ТОЛЬКО для секретов/юридических требований (1% случаев)
  - ✅ Добавлены критические проверки (`git ls-tree -r HEAD --name-only | wc -l` > 0) и механизмы защиты
- **ОПТИМИЗАЦИЯ (2026-02-27):** Исправлена медленная верификация push - заменен `git fetch origin` на `git update-ref` для мгновенного обновления tracking ref без сетевых запросов

