Initial own-git docs, configs, scripts, and hardening pack
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
# GitHub source
|
||||
GITHUB_TOKEN=ghp_xxx
|
||||
GITHUB_OWNER=your_github_user_or_org
|
||||
GITHUB_OWNER_TYPE=user
|
||||
|
||||
# Gitea target
|
||||
GITEA_URL=https://git.papatramp.ru
|
||||
GITEA_TOKEN=gitea_pat_xxx
|
||||
GITEA_OWNER=your_gitea_user_or_org
|
||||
GITEA_OWNER_TYPE=user
|
||||
|
||||
# Push strategy
|
||||
PUSH_WITH_SSH=true
|
||||
GITEA_SSH_HOST=git.papatramp.ru
|
||||
GITEA_SSH_PORT=2222
|
||||
# Required only for HTTPS push mode
|
||||
GITEA_USERNAME=your_gitea_username
|
||||
|
||||
# Optional behavior
|
||||
MIGRATE_LFS=true
|
||||
WORKDIR=./migration-workdir
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"ignorePatterns": [
|
||||
{
|
||||
"pattern": "^ssh://"
|
||||
},
|
||||
{
|
||||
"pattern": "^git@"
|
||||
}
|
||||
],
|
||||
"replacementPatterns": [],
|
||||
"timeout": "20s",
|
||||
"retryOn429": true,
|
||||
"retryCount": 2,
|
||||
"fallbackRetryDelay": "2s",
|
||||
"aliveStatusCodes": [200, 206, 301, 302, 303, 307, 308]
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
name: HAProxy Config Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "haproxy/**"
|
||||
- ".github/workflows/haproxy-lint.yml"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "haproxy/**"
|
||||
- ".github/workflows/haproxy-lint.yml"
|
||||
|
||||
jobs:
|
||||
validate:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install haproxy
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y haproxy
|
||||
|
||||
- name: Prepare include files and dummy cert
|
||||
run: |
|
||||
sudo mkdir -p /etc/haproxy /etc/haproxy/certs
|
||||
sudo cp haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg
|
||||
echo "127.0.0.1/32" | sudo tee /etc/haproxy/allowed_ips.lst >/dev/null
|
||||
echo "127.0.0.1/32" | sudo tee /etc/haproxy/allowed_ips_knock.lst >/dev/null
|
||||
openssl req -x509 -nodes -newkey rsa:2048 \
|
||||
-keyout /tmp/tls.key \
|
||||
-out /tmp/tls.crt \
|
||||
-days 1 \
|
||||
-subj "/CN=git.papatramp.ru"
|
||||
cat /tmp/tls.crt /tmp/tls.key | sudo tee /etc/haproxy/certs/papatramp.pem >/dev/null
|
||||
|
||||
- name: Validate haproxy cfg
|
||||
run: |
|
||||
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
|
||||
@@ -0,0 +1,48 @@
|
||||
name: Scripts And Docs Check
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "scripts/**"
|
||||
- "README.md"
|
||||
- "docs/**"
|
||||
- ".github/workflows/scripts-and-docs-check.yml"
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- "scripts/**"
|
||||
- "README.md"
|
||||
- "docs/**"
|
||||
- ".github/workflows/scripts-and-docs-check.yml"
|
||||
|
||||
jobs:
|
||||
shellcheck:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install shellcheck
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y shellcheck
|
||||
|
||||
- name: Run shellcheck
|
||||
run: |
|
||||
shellcheck scripts/*.sh
|
||||
|
||||
markdown-links:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check markdown links
|
||||
uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
with:
|
||||
use-quiet-mode: "yes"
|
||||
use-verbose-mode: "no"
|
||||
config-file: ".github/mlc_config.json"
|
||||
folder-path: "."
|
||||
file-extension: ".md"
|
||||
@@ -0,0 +1,3 @@
|
||||
migration-workdir/
|
||||
*.log
|
||||
*.tmp
|
||||
@@ -0,0 +1,41 @@
|
||||
SHELL := /usr/bin/env bash
|
||||
|
||||
.PHONY: help check-haproxy deploy-haproxy gitea-up gitea-restart migrate dry-run
|
||||
|
||||
help:
|
||||
@echo "Targets:"
|
||||
@echo " check-haproxy - validate local haproxy/haproxy.cfg"
|
||||
@echo " deploy-haproxy - copy cfg to /etc/haproxy and reload service"
|
||||
@echo " gitea-up - start Gitea via docker compose"
|
||||
@echo " gitea-restart - restart Gitea container"
|
||||
@echo " migrate - run GitHub -> Gitea migration"
|
||||
@echo " dry-run - show variables loaded from .env (safe preview)"
|
||||
|
||||
check-haproxy:
|
||||
sudo haproxy -c -f haproxy/haproxy.cfg
|
||||
|
||||
deploy-haproxy: check-haproxy
|
||||
sudo cp haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg
|
||||
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
|
||||
sudo systemctl reload haproxy
|
||||
|
||||
gitea-up:
|
||||
sudo mkdir -p /opt/gitea/data/gitea/conf
|
||||
sudo cp gitea/docker-compose.yml /opt/gitea/docker-compose.yml
|
||||
cd /opt/gitea && sudo docker compose up -d
|
||||
|
||||
gitea-restart:
|
||||
cd /opt/gitea && sudo docker compose restart gitea
|
||||
|
||||
migrate:
|
||||
@if [ ! -f .env ]; then echo ".env not found. Copy .env.example -> .env first"; exit 1; fi
|
||||
set -a && source .env && set +a && ./scripts/migrate-github-to-gitea.sh
|
||||
|
||||
dry-run:
|
||||
@if [ ! -f .env ]; then echo ".env not found. Copy .env.example -> .env first"; exit 1; fi
|
||||
set -a && source .env && set +a && \
|
||||
echo "GITHUB_OWNER=$$GITHUB_OWNER ($$GITHUB_OWNER_TYPE)" && \
|
||||
echo "GITEA_URL=$$GITEA_URL" && \
|
||||
echo "GITEA_OWNER=$$GITEA_OWNER ($$GITEA_OWNER_TYPE)" && \
|
||||
echo "PUSH_WITH_SSH=$$PUSH_WITH_SSH, GITEA_SSH_HOST=$$GITEA_SSH_HOST, GITEA_SSH_PORT=$$GITEA_SSH_PORT" && \
|
||||
echo "MIGRATE_LFS=$$MIGRATE_LFS"
|
||||
@@ -0,0 +1,276 @@
|
||||
# OwnGit: свой "аналог GitHub" на Ubuntu 24.04
|
||||
|
||||
Готовый комплект для поднятия собственного git-сервера (Gitea/Forgejo) за HAProxy:
|
||||
|
||||
- reverse proxy и TLS на HAProxy;
|
||||
- Gitea внутри сети;
|
||||
- SSH для `git push/pull`;
|
||||
- миграция репозиториев с GitHub (public + private).
|
||||
|
||||
## Состав репозитория
|
||||
|
||||
- `haproxy/haproxy.cfg` — полный конфиг HAProxy (с Gitea, Zabbix, Synology, RDP, SMTP).
|
||||
- `gitea/docker-compose.yml` — запуск Gitea в Docker.
|
||||
- `gitea/app.ini.example` — шаблон конфигурации Gitea.
|
||||
- `scripts/migrate-github-to-gitea.sh` — массовая миграция репозиториев GitHub в Gitea.
|
||||
- `scripts/preflight-check.sh` — быстрая проверка DNS/портов/healthz/SSH.
|
||||
- `scripts/backup-gitea.sh` — бэкап данных Gitea с ротацией.
|
||||
- `scripts/restore-gitea.sh` — восстановление Gitea из архива бэкапа.
|
||||
- `scripts/setup-ufw.sh` — базовая настройка UFW под эту схему портов.
|
||||
- `scripts/safe-apply-ssh-hardening.sh` — безопасное применение SSH hardening.
|
||||
- `scripts/safe-apply-ssh-lockdown.sh` — безопасное применение строгого SSH lockdown-профиля.
|
||||
- `scripts/bootstrap-own-git.sh` — one-shot установка стека (deps + Gitea + HAProxy + fail2ban + timer).
|
||||
- `.env.example` — шаблон переменных окружения для миграции.
|
||||
- `Makefile` — быстрые команды установки/проверки/миграции.
|
||||
- `docs/troubleshooting.md` — типовые проблемы и диагностика.
|
||||
- `docs/security-hardening.md` — чеклист усиления безопасности (P0/P1/P2).
|
||||
- `.github/workflows/haproxy-lint.yml` — CI-проверка синтаксиса `haproxy.cfg`.
|
||||
- `.github/workflows/scripts-and-docs-check.yml` — `shellcheck` и проверка markdown-ссылок.
|
||||
- `systemd/` — unit/timer для ежедневных бэкапов через systemd.
|
||||
- `fail2ban/jail.d/owngit.local` — готовый профиль fail2ban (sshd + recidive).
|
||||
- `ssh/sshd_config.d/90-owngit-hardening.conf` — шаблон безопасных SSH-настроек.
|
||||
- `ssh/sshd_config.d/95-owngit-lockdown.conf.example` — опциональный строгий профиль (AllowUsers/Match Address).
|
||||
|
||||
## Архитектура
|
||||
|
||||
- Публичный домен Gitea: `git.papatramp.ru`.
|
||||
- TLS-сертификат хранится и завершается на HAProxy (`:443`).
|
||||
- Внутренний доступ к Gitea: `192.168.128.100:3000`.
|
||||
- Git SSH: внешний `:2222` на HAProxy -> внутренний `192.168.128.100:2222`.
|
||||
|
||||
## 1) Установка Gitea
|
||||
|
||||
```bash
|
||||
sudo apt update
|
||||
sudo apt install -y docker.io docker-compose-plugin
|
||||
sudo systemctl enable --now docker
|
||||
|
||||
sudo mkdir -p /opt/gitea/data/gitea/conf
|
||||
sudo cp gitea/docker-compose.yml /opt/gitea/docker-compose.yml
|
||||
cd /opt/gitea
|
||||
sudo docker compose up -d
|
||||
```
|
||||
|
||||
Или one-shot bootstrap:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/bootstrap-own-git.sh
|
||||
sudo OWGIT_REPO_DIR=/opt/owngit scripts/bootstrap-own-git.sh
|
||||
```
|
||||
|
||||
Сначала безопасная проверка шагов без изменений:
|
||||
|
||||
```bash
|
||||
OWGIT_REPO_DIR=/opt/owngit scripts/bootstrap-own-git.sh --dry-run
|
||||
```
|
||||
|
||||
Полезные переменные bootstrap:
|
||||
|
||||
```bash
|
||||
SERVER_DOMAIN=git.papatramp.ru
|
||||
GITEA_LAN_IP=192.168.128.100
|
||||
INSTALL_UFW=false
|
||||
ENABLE_FAIL2BAN=true
|
||||
ENABLE_BACKUP_TIMER=true
|
||||
```
|
||||
|
||||
Положить `app.ini`:
|
||||
|
||||
```bash
|
||||
sudo cp gitea/app.ini.example /opt/gitea/data/gitea/conf/app.ini
|
||||
sudo nano /opt/gitea/data/gitea/conf/app.ini
|
||||
sudo docker compose restart gitea
|
||||
```
|
||||
|
||||
Сгенерировать секреты:
|
||||
|
||||
```bash
|
||||
openssl rand -hex 32
|
||||
openssl rand -hex 32
|
||||
```
|
||||
|
||||
## 2) Установка HAProxy конфига
|
||||
|
||||
```bash
|
||||
sudo cp haproxy/haproxy.cfg /etc/haproxy/haproxy.cfg
|
||||
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
|
||||
sudo systemctl reload haproxy
|
||||
```
|
||||
|
||||
Важно: сертификат `papatramp.pem` должен включать SAN для `git.papatramp.ru`.
|
||||
|
||||
## 3) Нужно ли SSL на самом Gitea?
|
||||
|
||||
Обычно нет. Достаточно TLS на HAProxy:
|
||||
|
||||
- клиент <-> HAProxy: HTTPS (с сертификатом);
|
||||
- HAProxy <-> Gitea: HTTP по LAN.
|
||||
|
||||
SSL на Gitea нужен только если нужен end-to-end TLS внутри сети или Gitea публикуется напрямую без HAProxy.
|
||||
|
||||
## 4) Работа и из интернета, и из локалки
|
||||
|
||||
Рекомендуется единый адрес: `https://git.papatramp.ru`.
|
||||
|
||||
Чтобы это работало из LAN:
|
||||
|
||||
- либо Split DNS: `git.papatramp.ru -> LAN IP HAProxy`;
|
||||
- либо Hairpin NAT на роутере.
|
||||
|
||||
## 5) SSH-клиент (`~/.ssh/config`)
|
||||
|
||||
```sshconfig
|
||||
Host git-papatramp
|
||||
HostName git.papatramp.ru
|
||||
User git
|
||||
Port 2222
|
||||
IdentityFile ~/.ssh/id_ed25519
|
||||
IdentitiesOnly yes
|
||||
ServerAliveInterval 30
|
||||
ServerAliveCountMax 6
|
||||
```
|
||||
|
||||
Проверка:
|
||||
|
||||
```bash
|
||||
ssh -T git-papatramp
|
||||
```
|
||||
|
||||
## 6) Миграция репозиториев с GitHub
|
||||
|
||||
Скрипт в `scripts/migrate-github-to-gitea.sh`:
|
||||
|
||||
- получает список реп через GitHub API;
|
||||
- создает репозитории в Gitea API;
|
||||
- делает `git clone --mirror` + `git push --mirror`;
|
||||
- умеет мигрировать LFS (опционально).
|
||||
|
||||
Пример запуска:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/migrate-github-to-gitea.sh
|
||||
|
||||
export GITHUB_TOKEN='ghp_...'
|
||||
export GITHUB_OWNER='your_github_user_or_org'
|
||||
export GITHUB_OWNER_TYPE='user' # или org
|
||||
|
||||
export GITEA_URL='https://git.papatramp.ru'
|
||||
export GITEA_TOKEN='gitea_pat'
|
||||
export GITEA_OWNER='your_gitea_user_or_org'
|
||||
export GITEA_OWNER_TYPE='user' # или org
|
||||
|
||||
export PUSH_WITH_SSH='true'
|
||||
export GITEA_SSH_HOST='git.papatramp.ru'
|
||||
export GITEA_SSH_PORT='2222'
|
||||
export MIGRATE_LFS='true'
|
||||
|
||||
./scripts/migrate-github-to-gitea.sh
|
||||
```
|
||||
|
||||
## 7) Быстрые проверки
|
||||
|
||||
```bash
|
||||
curl -I http://192.168.128.100:3000
|
||||
curl -I https://git.papatramp.ru
|
||||
nc -zv git.papatramp.ru 2222
|
||||
```
|
||||
|
||||
Если всё корректно:
|
||||
|
||||
- web UI открывается по `https://git.papatramp.ru`;
|
||||
- `ssh -T git-papatramp` достигает Gitea;
|
||||
- `git clone/push` работает по SSH/HTTPS.
|
||||
|
||||
## 8) Полезные команды через Makefile
|
||||
|
||||
```bash
|
||||
make help
|
||||
make check-haproxy
|
||||
make deploy-haproxy
|
||||
make gitea-up
|
||||
make gitea-restart
|
||||
```
|
||||
|
||||
Запуск миграции через `.env`:
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nano .env
|
||||
make dry-run
|
||||
make migrate
|
||||
```
|
||||
|
||||
## 9) Preflight и backup
|
||||
|
||||
```bash
|
||||
chmod +x scripts/preflight-check.sh scripts/backup-gitea.sh
|
||||
./scripts/preflight-check.sh
|
||||
```
|
||||
|
||||
Запуск бэкапа вручную:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/backup-gitea.sh
|
||||
```
|
||||
|
||||
Пример cron (ежедневно в 03:10):
|
||||
|
||||
```bash
|
||||
10 3 * * * /opt/owngit/scripts/backup-gitea.sh >> /var/log/gitea-backup.log 2>&1
|
||||
```
|
||||
|
||||
Восстановление:
|
||||
|
||||
```bash
|
||||
sudo ./scripts/restore-gitea.sh /opt/backups/gitea/gitea-backup-YYYYMMDD-HHMMSS.tar.gz
|
||||
```
|
||||
|
||||
## 10) Автобэкапы через systemd timer
|
||||
|
||||
Установка:
|
||||
|
||||
```bash
|
||||
sudo cp systemd/gitea-backup.service /etc/systemd/system/gitea-backup.service
|
||||
sudo cp systemd/gitea-backup.timer /etc/systemd/system/gitea-backup.timer
|
||||
sudo systemctl daemon-reload
|
||||
sudo systemctl enable --now gitea-backup.timer
|
||||
```
|
||||
|
||||
Проверка:
|
||||
|
||||
```bash
|
||||
systemctl list-timers --all | grep gitea-backup
|
||||
sudo systemctl status gitea-backup.timer --no-pager
|
||||
```
|
||||
|
||||
## 11) Hardening (firewall + fail2ban)
|
||||
|
||||
```bash
|
||||
chmod +x scripts/setup-ufw.sh
|
||||
sudo ./scripts/setup-ufw.sh
|
||||
```
|
||||
|
||||
Настройка fail2ban:
|
||||
|
||||
```bash
|
||||
sudo apt install -y fail2ban
|
||||
sudo cp fail2ban/jail.d/owngit.local /etc/fail2ban/jail.d/owngit.local
|
||||
sudo systemctl enable --now fail2ban
|
||||
sudo fail2ban-client status
|
||||
```
|
||||
|
||||
Подробный чеклист и приоритеты см. в `docs/security-hardening.md`.
|
||||
|
||||
Применение SSH hardening:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/safe-apply-ssh-hardening.sh
|
||||
sudo ./scripts/safe-apply-ssh-hardening.sh
|
||||
```
|
||||
|
||||
Опциональный lockdown:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/safe-apply-ssh-lockdown.sh
|
||||
sudo ./scripts/safe-apply-ssh-lockdown.sh
|
||||
```
|
||||
@@ -0,0 +1,87 @@
|
||||
# Security Hardening Checklist
|
||||
|
||||
Этот список рассчитан на инфраструктуру из этого репозитория:
|
||||
|
||||
- HAProxy как edge reverse proxy;
|
||||
- Gitea в Docker;
|
||||
- публичные порты `80`, `443`, `2222`, `25`, `3389`.
|
||||
|
||||
## Приоритет P0 (сделать в первую очередь)
|
||||
|
||||
- Обновить систему:
|
||||
- `sudo apt update && sudo apt full-upgrade -y`
|
||||
- Включить автообновления security-пакетов (`unattended-upgrades`).
|
||||
- Сменить/усилить пароли админов, включить 2FA в Gitea.
|
||||
- Проверить `ROOT_URL=https://git.papatramp.ru/` в `app.ini`.
|
||||
- Убедиться, что TLS-сертификат на HAProxy содержит SAN `git.papatramp.ru`.
|
||||
- Ограничить доступ к закрытым сервисам через `allowed_ips.lst`.
|
||||
|
||||
## Приоритет P1 (в течение дня)
|
||||
|
||||
- Настроить firewall (UFW):
|
||||
- `sudo ./scripts/setup-ufw.sh`
|
||||
- Настроить fail2ban:
|
||||
- `sudo apt install -y fail2ban`
|
||||
- `sudo cp fail2ban/jail.d/owngit.local /etc/fail2ban/jail.d/owngit.local`
|
||||
- `sudo systemctl enable --now fail2ban`
|
||||
- `sudo fail2ban-client status`
|
||||
- Отключить root login по SSH и парольный вход:
|
||||
- использовать безопасный скрипт:
|
||||
- `sudo ./scripts/safe-apply-ssh-hardening.sh`
|
||||
- шаблон конфига: `ssh/sshd_config.d/90-owngit-hardening.conf`
|
||||
|
||||
## Приоритет P2 (регулярная эксплуатация)
|
||||
|
||||
- Включить ежедневные бэкапы (`systemd` timer из `systemd/`).
|
||||
- Раз в месяц делать тестовое восстановление через `scripts/restore-gitea.sh`.
|
||||
- Проверять fail2ban:
|
||||
- `sudo fail2ban-client status sshd`
|
||||
- Проверять логи HAProxy/Gitea:
|
||||
- ошибки 4xx/5xx,
|
||||
- всплески соединений и brute-force.
|
||||
- Хранить бэкапы вне сервера (NAS/S3/другой хост).
|
||||
|
||||
## Минимальная проверка после hardening
|
||||
|
||||
```bash
|
||||
sudo ufw status verbose
|
||||
sudo fail2ban-client status
|
||||
curl -I https://git.papatramp.ru
|
||||
ssh -T -p 2222 git@git.papatramp.ru
|
||||
```
|
||||
|
||||
## Безопасное применение SSH hardening
|
||||
|
||||
```bash
|
||||
chmod +x scripts/safe-apply-ssh-hardening.sh
|
||||
sudo ./scripts/safe-apply-ssh-hardening.sh
|
||||
```
|
||||
|
||||
Скрипт:
|
||||
|
||||
- делает backup текущих SSH-конфигов;
|
||||
- устанавливает include-конфиг в `/etc/ssh/sshd_config.d/`;
|
||||
- проверяет синтаксис `sshd -t`;
|
||||
- перезагружает ssh только при валидной конфигурации.
|
||||
|
||||
## Опциональный Lockdown профиль
|
||||
|
||||
Если нужен строгий доступ только для отдельных пользователей/сетей:
|
||||
|
||||
1. Отредактируй шаблон:
|
||||
- `ssh/sshd_config.d/95-owngit-lockdown.conf.example`
|
||||
2. Укажи реальные логины в `AllowUsers`.
|
||||
3. Применяй только безопасным скриптом:
|
||||
|
||||
```bash
|
||||
chmod +x scripts/safe-apply-ssh-lockdown.sh
|
||||
sudo ./scripts/safe-apply-ssh-lockdown.sh
|
||||
```
|
||||
|
||||
Что делает скрипт:
|
||||
|
||||
- проверяет наличие `AllowUsers` в шаблоне;
|
||||
- ставит конфиг в `/etc/ssh/sshd_config.d/95-owngit-lockdown.conf`;
|
||||
- валидирует `sshd -t`;
|
||||
- делает reload только при валидной конфигурации;
|
||||
- сохраняет backup предыдущего lockdown-файла.
|
||||
@@ -0,0 +1,121 @@
|
||||
# Troubleshooting
|
||||
|
||||
## 1) `https://git.papatramp.ru` не открывается
|
||||
|
||||
Проверь DNS и маршрутизацию:
|
||||
|
||||
```bash
|
||||
dig +short git.papatramp.ru
|
||||
```
|
||||
|
||||
- из интернета должен резолвиться во внешний IP;
|
||||
- из локалки лучше Split DNS на LAN IP HAProxy.
|
||||
|
||||
Проверь HAProxy:
|
||||
|
||||
```bash
|
||||
sudo haproxy -c -f /etc/haproxy/haproxy.cfg
|
||||
sudo systemctl status haproxy --no-pager
|
||||
```
|
||||
|
||||
## 2) 404 от HAProxy
|
||||
|
||||
Причина: не совпадает `Host`, запрос уходит в `bk_http_default`.
|
||||
|
||||
Проверка:
|
||||
|
||||
```bash
|
||||
curl -I -H "Host: git.papatramp.ru" http://127.0.0.1
|
||||
```
|
||||
|
||||
Убедись, что есть ACL:
|
||||
|
||||
- `acl host_git hdr(host) -i git.papatramp.ru`
|
||||
- `use_backend bk_gitea if host_git`
|
||||
|
||||
## 3) 503 от HAProxy на Gitea
|
||||
|
||||
Причина: backend `bk_gitea` down.
|
||||
|
||||
Проверки:
|
||||
|
||||
```bash
|
||||
curl -I http://192.168.128.100:3000
|
||||
curl -I http://192.168.128.100:3000/api/healthz
|
||||
```
|
||||
|
||||
Если не отвечает:
|
||||
|
||||
```bash
|
||||
cd /opt/gitea
|
||||
sudo docker compose ps
|
||||
sudo docker compose logs --tail=200 gitea
|
||||
```
|
||||
|
||||
## 4) SSH push не работает (`Permission denied (publickey)`)
|
||||
|
||||
Проверь порт и доступ:
|
||||
|
||||
```bash
|
||||
nc -zv git.papatramp.ru 2222
|
||||
ssh -vvv -p 2222 git@git.papatramp.ru
|
||||
```
|
||||
|
||||
Убедись:
|
||||
|
||||
- публичный ключ добавлен в Gitea аккаунт;
|
||||
- в `~/.ssh/config` правильный `HostName`, `Port`, `IdentityFile`;
|
||||
- backend `bk_git_ssh` смотрит на правильный IP/порт.
|
||||
|
||||
## 5) В локалке не работает, а из интернета работает
|
||||
|
||||
Обычно это DNS/NAT loopback:
|
||||
|
||||
- настрой Split DNS (`git.papatramp.ru -> LAN IP HAProxy`);
|
||||
- либо включи Hairpin NAT на роутере.
|
||||
|
||||
## 6) Миграция не создает репозитории в Gitea
|
||||
|
||||
Проверь токен и права:
|
||||
|
||||
- `GITEA_TOKEN` должен иметь право создавать репозитории;
|
||||
- для org нужен доступ к организации;
|
||||
- `GITEA_OWNER_TYPE` должен соответствовать `user`/`org`.
|
||||
|
||||
Проверка API:
|
||||
|
||||
```bash
|
||||
curl -H "Authorization: token $GITEA_TOKEN" "$GITEA_URL/api/v1/user"
|
||||
```
|
||||
|
||||
## 7) Private репозитории GitHub не видны в миграции
|
||||
|
||||
Проверь `GITHUB_TOKEN`:
|
||||
|
||||
- токен должен иметь права на чтение приватных репозиториев;
|
||||
- `GITHUB_OWNER` и `GITHUB_OWNER_TYPE` должны быть корректными.
|
||||
|
||||
## 8) LFS не перенесся
|
||||
|
||||
Включи:
|
||||
|
||||
```bash
|
||||
export MIGRATE_LFS=true
|
||||
```
|
||||
|
||||
И проверь наличие `git-lfs`:
|
||||
|
||||
```bash
|
||||
git lfs version
|
||||
```
|
||||
|
||||
## 9) Кривые редиректы/URL в Gitea
|
||||
|
||||
Проверь `ROOT_URL` в `app.ini`:
|
||||
|
||||
```ini
|
||||
[server]
|
||||
ROOT_URL = https://git.papatramp.ru/
|
||||
```
|
||||
|
||||
И заголовки от HAProxy в `bk_gitea` (`X-Forwarded-Proto`, `X-Forwarded-Host`, `Host`).
|
||||
@@ -0,0 +1,18 @@
|
||||
[DEFAULT]
|
||||
bantime = 1h
|
||||
findtime = 10m
|
||||
maxretry = 5
|
||||
backend = systemd
|
||||
|
||||
[sshd]
|
||||
enabled = true
|
||||
port = ssh
|
||||
logpath = %(sshd_log)s
|
||||
maxretry = 5
|
||||
|
||||
[recidive]
|
||||
enabled = true
|
||||
logpath = /var/log/fail2ban.log
|
||||
bantime = 1w
|
||||
findtime = 1d
|
||||
maxretry = 5
|
||||
@@ -0,0 +1,39 @@
|
||||
APP_NAME = Gitea
|
||||
RUN_MODE = prod
|
||||
RUN_USER = git
|
||||
|
||||
[server]
|
||||
DOMAIN = git.papatramp.ru
|
||||
SSH_DOMAIN = git.papatramp.ru
|
||||
HTTP_PORT = 3000
|
||||
ROOT_URL = https://git.papatramp.ru/
|
||||
DISABLE_SSH = false
|
||||
START_SSH_SERVER = true
|
||||
SSH_PORT = 2222
|
||||
SSH_LISTEN_PORT = 2222
|
||||
PROTOCOL = http
|
||||
|
||||
[service]
|
||||
DISABLE_REGISTRATION = true
|
||||
REQUIRE_SIGNIN_VIEW = false
|
||||
REGISTER_EMAIL_CONFIRM = false
|
||||
ENABLE_NOTIFY_MAIL = false
|
||||
ALLOW_ONLY_EXTERNAL_REGISTRATION = false
|
||||
|
||||
[security]
|
||||
INSTALL_LOCK = true
|
||||
SECRET_KEY = CHANGE_ME_LONG_RANDOM_SECRET
|
||||
INTERNAL_TOKEN = CHANGE_ME_LONG_RANDOM_TOKEN
|
||||
PASSWORD_HASH_ALGO = argon2
|
||||
|
||||
[database]
|
||||
DB_TYPE = sqlite3
|
||||
PATH = /data/gitea/gitea.db
|
||||
|
||||
[repository]
|
||||
ROOT = /data/git/repositories
|
||||
|
||||
[log]
|
||||
MODE = console
|
||||
LEVEL = Info
|
||||
ROOT_PATH = /data/gitea/log
|
||||
@@ -0,0 +1,16 @@
|
||||
services:
|
||||
gitea:
|
||||
image: gitea/gitea:latest
|
||||
container_name: gitea
|
||||
restart: unless-stopped
|
||||
environment:
|
||||
- USER_UID=1000
|
||||
- USER_GID=1000
|
||||
- TZ=Europe/Moscow
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "2222:2222"
|
||||
volumes:
|
||||
- /opt/gitea/data:/data
|
||||
- /etc/timezone:/etc/timezone:ro
|
||||
- /etc/localtime:/etc/localtime:ro
|
||||
@@ -0,0 +1,199 @@
|
||||
# HAProxy — домашняя сеть papatramp.ru
|
||||
# Версия синтаксиса: HAProxy 2.4+
|
||||
#
|
||||
# TLS завершается на HAProxy.
|
||||
# Gitea снаружи: git.papatramp.ru
|
||||
# Gitea внутри: 192.168.128.100 (или git.papatramp.ru, если локально резолвится в LAN IP).
|
||||
|
||||
global
|
||||
log stdout format raw local0
|
||||
maxconn 40000
|
||||
# tune.ssl.default-dh-param 2048
|
||||
|
||||
defaults
|
||||
mode http
|
||||
log global
|
||||
option httplog
|
||||
option dontlognull
|
||||
timeout connect 10s
|
||||
timeout client 300s
|
||||
timeout server 300s
|
||||
timeout http-request 10s
|
||||
|
||||
# --- HTTP (80)
|
||||
frontend fe_http
|
||||
bind *:80
|
||||
mode http
|
||||
option forwardfor except 127.0.0.0/8
|
||||
|
||||
acl path_acme path_beg /.well-known/acme-challenge/
|
||||
acl trusted src -f /etc/haproxy/allowed_ips.lst
|
||||
acl trusted src -f /etc/haproxy/allowed_ips_knock.lst
|
||||
acl host_photo hdr(host) -i photostation.papatramp.ru
|
||||
acl host_oldbox hdr(host) -i oldbox.papatramp.ru
|
||||
acl host_git hdr(host) -i git.papatramp.ru
|
||||
|
||||
# ACME не блокируем по trusted
|
||||
http-request deny deny_status 403 if host_photo !trusted !path_acme
|
||||
http-request deny deny_status 403 if host_oldbox !trusted !path_acme
|
||||
|
||||
use_backend bk_acme if path_acme
|
||||
use_backend bk_gitea if host_git
|
||||
use_backend bk_photostation if host_photo
|
||||
use_backend bk_oldbox if host_oldbox
|
||||
use_backend bk_zabbix if { hdr(host) -i zabbix.papatramp.ru }
|
||||
|
||||
default_backend bk_http_default
|
||||
|
||||
# --- HTTPS (443)
|
||||
frontend fe_https
|
||||
# Только HTTP/1.1 к клиенту (как в текущем рабочем варианте)
|
||||
bind *:443 ssl crt /etc/haproxy/certs/papatramp.pem alpn http/1.1
|
||||
mode http
|
||||
option forwardfor except 127.0.0.0/8
|
||||
|
||||
acl trusted src -f /etc/haproxy/allowed_ips.lst
|
||||
acl trusted src -f /etc/haproxy/allowed_ips_knock.lst
|
||||
|
||||
acl host_photo hdr(host) -i photostation.papatramp.ru
|
||||
acl host_oldbox hdr(host) -i oldbox.papatramp.ru
|
||||
acl host_git hdr(host) -i git.papatramp.ru
|
||||
|
||||
http-request deny deny_status 403 if host_photo !trusted
|
||||
http-request deny deny_status 403 if host_oldbox !trusted
|
||||
|
||||
use_backend bk_gitea if host_git
|
||||
use_backend bk_photostation if host_photo
|
||||
use_backend bk_oldbox if host_oldbox
|
||||
use_backend bk_zabbix if { hdr(host) -i zabbix.papatramp.ru }
|
||||
|
||||
default_backend bk_http_default
|
||||
|
||||
# Заглушка для неизвестного Host
|
||||
backend bk_http_default
|
||||
mode http
|
||||
http-request deny deny_status 404
|
||||
|
||||
# ACME webroot через локальный nginx
|
||||
backend bk_acme
|
||||
mode http
|
||||
server nginx_acme 127.0.0.1:8080
|
||||
|
||||
# Synology Photo Station / DSM
|
||||
backend bk_photostation
|
||||
mode http
|
||||
balance roundrobin
|
||||
http-request set-header Host 192.168.128.190:5003
|
||||
http-request set-header X-Forwarded-Host %[req.hdr(Host)]
|
||||
http-request set-header X-Forwarded-Proto https if { ssl_fc }
|
||||
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
|
||||
http-request set-header X-Forwarded-Port 443 if { ssl_fc }
|
||||
http-request set-header X-Forwarded-Port 80 if !{ ssl_fc }
|
||||
http-response replace-value Location ^https?://192\.168\.128\.190:5003/(.*)$ https://photostation.papatramp.ru/\1
|
||||
http-response replace-value Location ^https?://192\.168\.128\.190:5000/(.*)$ https://photostation.papatramp.ru/\1
|
||||
http-response replace-value Location ^https?://192\.168\.128\.190/(.*)$ https://photostation.papatramp.ru/\1
|
||||
server syno190 192.168.128.190:5003 ssl verify none check inter 10s rise 2 fall 3
|
||||
# Вариант только HTTP к NAS:
|
||||
# server syno190 192.168.128.190:5000 check inter 10s rise 2 fall 3
|
||||
|
||||
backend bk_oldbox
|
||||
mode http
|
||||
balance roundrobin
|
||||
http-request set-header Host 192.168.128.180:5001
|
||||
http-request set-header X-Forwarded-Host %[req.hdr(Host)]
|
||||
http-request set-header X-Forwarded-Proto https if { ssl_fc }
|
||||
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
|
||||
http-request set-header X-Forwarded-Port 443 if { ssl_fc }
|
||||
http-request set-header X-Forwarded-Port 80 if !{ ssl_fc }
|
||||
http-response replace-value Location ^https?://192\.168\.128\.180:5001/(.*)$ https://oldbox.papatramp.ru/\1
|
||||
http-response replace-value Location ^https?://192\.168\.128\.180:5000/(.*)$ https://oldbox.papatramp.ru/\1
|
||||
http-response replace-value Location ^https?://192\.168\.128\.180/(.*)$ https://oldbox.papatramp.ru/\1
|
||||
server syno180 192.168.128.180:5001 ssl verify none check inter 10s rise 2 fall 3
|
||||
# server syno180 192.168.128.180:5000 check inter 10s rise 2 fall 3
|
||||
|
||||
# Zabbix
|
||||
backend bk_zabbix
|
||||
mode http
|
||||
balance roundrobin
|
||||
option httpchk
|
||||
http-check send meth GET uri / ver HTTP/1.1 hdr Host 192.168.128.52
|
||||
http-check expect rstatus ^[23]
|
||||
http-request set-header X-Forwarded-Proto https if { ssl_fc }
|
||||
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
|
||||
http-request set-header X-Forwarded-Port 443 if { ssl_fc }
|
||||
http-request set-header X-Forwarded-Port 80 if !{ ssl_fc }
|
||||
http-request set-header Host 192.168.128.52
|
||||
server zabbix1 192.168.128.52:80 check inter 15s rise 2 fall 3
|
||||
|
||||
# Gitea/Forgejo UI + Git over HTTP(S)
|
||||
backend bk_gitea
|
||||
mode http
|
||||
option httpchk
|
||||
http-check send meth GET uri /api/healthz ver HTTP/1.1 hdr Host git.papatramp.ru
|
||||
http-check expect status 200
|
||||
http-request set-header Host git.papatramp.ru
|
||||
http-request set-header X-Forwarded-Host %[req.hdr(Host)]
|
||||
http-request set-header X-Forwarded-Proto https if { ssl_fc }
|
||||
http-request set-header X-Forwarded-Proto http if !{ ssl_fc }
|
||||
http-request set-header X-Forwarded-Port 443 if { ssl_fc }
|
||||
http-request set-header X-Forwarded-Port 80 if !{ ssl_fc }
|
||||
server gitea1 192.168.128.100:3000 check inter 10s rise 2 fall 3
|
||||
# Если внутри хочешь ходить по имени:
|
||||
# server gitea1 git.papatramp.ru:3000 check inter 10s rise 2 fall 3
|
||||
|
||||
# Git SSH (внешний 2222 -> внутренний Gitea 2222)
|
||||
frontend fe_git_ssh
|
||||
bind *:2222
|
||||
mode tcp
|
||||
option tcplog
|
||||
# При необходимости ограничения по IP:
|
||||
# acl ssh_trusted src -f /etc/haproxy/allowed_ips.lst
|
||||
# acl ssh_trusted src -f /etc/haproxy/allowed_ips_knock.lst
|
||||
# tcp-request connection reject if !ssh_trusted
|
||||
default_backend bk_git_ssh
|
||||
|
||||
backend bk_git_ssh
|
||||
mode tcp
|
||||
server gitea_ssh 192.168.128.100:2222 check inter 10s rise 2 fall 3
|
||||
# Вариант через имя:
|
||||
# server gitea_ssh git.papatramp.ru:2222 check inter 10s rise 2 fall 3
|
||||
|
||||
# --- RDP (TCP)
|
||||
frontend fe_rdp_papa
|
||||
bind *:3389
|
||||
mode tcp
|
||||
option tcplog
|
||||
acl rdp_trusted src -f /etc/haproxy/allowed_ips.lst
|
||||
acl rdp_trusted src -f /etc/haproxy/allowed_ips_knock.lst
|
||||
tcp-request connection reject if !rdp_trusted
|
||||
default_backend bk_rdp_papa
|
||||
|
||||
backend bk_rdp_papa
|
||||
mode tcp
|
||||
server win_papa 192.168.128.5:3389 check inter 5s rise 2 fall 3
|
||||
|
||||
# Второй RDP (:3390 -> 192.168.128.30) — пример
|
||||
#frontend fe_rdp_second
|
||||
# bind *:3390
|
||||
# mode tcp
|
||||
# option tcplog
|
||||
# acl rdp_trusted src -f /etc/haproxy/allowed_ips.lst
|
||||
# acl rdp_trusted src -f /etc/haproxy/allowed_ips_knock.lst
|
||||
# tcp-request connection reject if !rdp_trusted
|
||||
# default_backend bk_rdp_second
|
||||
#
|
||||
#backend bk_rdp_second
|
||||
# mode tcp
|
||||
# server win_rdp2 192.168.128.30:3389 check inter 5s rise 2 fall 3
|
||||
|
||||
# --- SMTP (TCP/25)
|
||||
frontend fe_smtp
|
||||
bind *:25
|
||||
mode tcp
|
||||
option tcplog
|
||||
default_backend bk_smtp
|
||||
|
||||
backend bk_smtp
|
||||
mode tcp
|
||||
option smtpchk
|
||||
server mail_mx 192.168.128.80:25 check inter 15s rise 2 fall 3
|
||||
@@ -0,0 +1,48 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Резервное копирование Gitea data dir + app.ini с простой ротацией.
|
||||
# По умолчанию:
|
||||
# - источник: /opt/gitea/data
|
||||
# - бэкапы: /opt/backups/gitea
|
||||
# - хранить: 14 архивов
|
||||
|
||||
GITEA_DATA_DIR="${GITEA_DATA_DIR:-/opt/gitea/data}"
|
||||
BACKUP_DIR="${BACKUP_DIR:-/opt/backups/gitea}"
|
||||
RETENTION="${RETENTION:-14}"
|
||||
DATE_TAG="$(date +%Y%m%d-%H%M%S)"
|
||||
ARCHIVE_NAME="gitea-backup-${DATE_TAG}.tar.gz"
|
||||
TMP_DIR="${BACKUP_DIR}/.tmp-${DATE_TAG}"
|
||||
|
||||
mkdir -p "$BACKUP_DIR"
|
||||
mkdir -p "$TMP_DIR"
|
||||
|
||||
echo "[INFO] Backup started: $DATE_TAG"
|
||||
echo "[INFO] Source: $GITEA_DATA_DIR"
|
||||
echo "[INFO] Target: $BACKUP_DIR/$ARCHIVE_NAME"
|
||||
|
||||
if [[ ! -d "$GITEA_DATA_DIR" ]]; then
|
||||
echo "[ERR] Source directory not found: $GITEA_DATA_DIR"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Копируем только нужное в staging, чтобы архив был чистым.
|
||||
cp -a "$GITEA_DATA_DIR" "$TMP_DIR/data"
|
||||
|
||||
tar -C "$TMP_DIR" -czf "${BACKUP_DIR}/${ARCHIVE_NAME}" data
|
||||
rm -rf "$TMP_DIR"
|
||||
|
||||
echo "[OK] Archive created: ${BACKUP_DIR}/${ARCHIVE_NAME}"
|
||||
|
||||
# Ротация по количеству файлов (новые сохраняем, старые удаляем).
|
||||
mapfile -t OLD_BACKUPS < <(ls -1t "${BACKUP_DIR}"/gitea-backup-*.tar.gz 2>/dev/null || true)
|
||||
COUNT="${#OLD_BACKUPS[@]}"
|
||||
if (( COUNT > RETENTION )); then
|
||||
echo "[INFO] Retention: keep newest ${RETENTION}, remove $((COUNT - RETENTION))"
|
||||
for (( i=RETENTION; i<COUNT; i++ )); do
|
||||
rm -f "${OLD_BACKUPS[$i]}"
|
||||
echo "[INFO] Removed old backup: ${OLD_BACKUPS[$i]}"
|
||||
done
|
||||
fi
|
||||
|
||||
echo "[OK] Backup finished"
|
||||
@@ -0,0 +1,246 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# One-shot bootstrap для own-git стека:
|
||||
# - устанавливает зависимости;
|
||||
# - разворачивает Gitea через docker compose;
|
||||
# - раскладывает конфиги HAProxy/fail2ban/systemd;
|
||||
# - включает timer бэкапов;
|
||||
# - печатает пост-инсталляционный чеклист.
|
||||
#
|
||||
# ВНИМАНИЕ:
|
||||
# 1) Проверьте и отредактируйте переменные ниже перед запуском.
|
||||
# 2) Скрипт рассчитан на Ubuntu 24.04+.
|
||||
# 3) Запускать от root (sudo).
|
||||
|
||||
SERVER_DOMAIN="${SERVER_DOMAIN:-git.papatramp.ru}"
|
||||
GITEA_LAN_IP="${GITEA_LAN_IP:-192.168.128.100}"
|
||||
OWGIT_REPO_DIR="${OWGIT_REPO_DIR:-/opt/owngit}"
|
||||
GITEA_BASE_DIR="${GITEA_BASE_DIR:-/opt/gitea}"
|
||||
INSTALL_UFW="${INSTALL_UFW:-false}"
|
||||
ENABLE_FAIL2BAN="${ENABLE_FAIL2BAN:-true}"
|
||||
ENABLE_BACKUP_TIMER="${ENABLE_BACKUP_TIMER:-true}"
|
||||
DRY_RUN="${DRY_RUN:-false}"
|
||||
|
||||
log() { echo "[INFO] $*"; }
|
||||
ok() { echo "[OK] $*"; }
|
||||
warn() { echo "[WARN] $*"; }
|
||||
err() { echo "[ERR] $*"; }
|
||||
|
||||
usage() {
|
||||
cat <<EOF
|
||||
Usage: $0 [--dry-run]
|
||||
|
||||
Options:
|
||||
--dry-run показать все шаги без изменений в системе
|
||||
-h, --help показать эту справку
|
||||
EOF
|
||||
}
|
||||
|
||||
run_cmd() {
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "[DRY] $*"
|
||||
return 0
|
||||
fi
|
||||
"$@"
|
||||
}
|
||||
|
||||
run_sh() {
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
echo "[DRY] $*"
|
||||
return 0
|
||||
fi
|
||||
bash -lc "$*"
|
||||
}
|
||||
|
||||
require_root() {
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
warn "DRY_RUN=true: проверка root пропущена"
|
||||
return
|
||||
fi
|
||||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||
err "Запустите через sudo."
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
require_paths() {
|
||||
local missing=0
|
||||
for p in \
|
||||
"$OWGIT_REPO_DIR/gitea/docker-compose.yml" \
|
||||
"$OWGIT_REPO_DIR/gitea/app.ini.example" \
|
||||
"$OWGIT_REPO_DIR/haproxy/haproxy.cfg" \
|
||||
"$OWGIT_REPO_DIR/systemd/gitea-backup.service" \
|
||||
"$OWGIT_REPO_DIR/systemd/gitea-backup.timer" \
|
||||
"$OWGIT_REPO_DIR/fail2ban/jail.d/owngit.local"; do
|
||||
if [[ ! -f "$p" ]]; then
|
||||
err "Не найден файл: $p"
|
||||
missing=1
|
||||
fi
|
||||
done
|
||||
[[ "$missing" -eq 0 ]] || exit 1
|
||||
}
|
||||
|
||||
install_packages() {
|
||||
log "Устанавливаю зависимости..."
|
||||
run_cmd apt-get update
|
||||
run_cmd apt-get install -y \
|
||||
ca-certificates curl jq git openssl \
|
||||
docker.io docker-compose-plugin \
|
||||
haproxy
|
||||
ok "Пакеты установлены"
|
||||
}
|
||||
|
||||
setup_dirs() {
|
||||
log "Создаю рабочие директории..."
|
||||
run_cmd mkdir -p "$GITEA_BASE_DIR/data/gitea/conf"
|
||||
run_cmd mkdir -p /etc/haproxy/certs
|
||||
ok "Директории готовы"
|
||||
}
|
||||
|
||||
deploy_gitea() {
|
||||
log "Разворачиваю Gitea compose..."
|
||||
run_cmd cp -a "$OWGIT_REPO_DIR/gitea/docker-compose.yml" "$GITEA_BASE_DIR/docker-compose.yml"
|
||||
|
||||
if [[ ! -f "$GITEA_BASE_DIR/data/gitea/conf/app.ini" ]]; then
|
||||
run_cmd cp -a "$OWGIT_REPO_DIR/gitea/app.ini.example" "$GITEA_BASE_DIR/data/gitea/conf/app.ini"
|
||||
warn "Создан app.ini из шаблона. Обязательно замените SECRET_KEY и INTERNAL_TOKEN."
|
||||
fi
|
||||
|
||||
run_cmd systemctl enable --now docker
|
||||
run_sh "cd '$GITEA_BASE_DIR' && docker compose up -d"
|
||||
ok "Gitea контейнер запущен"
|
||||
}
|
||||
|
||||
deploy_haproxy() {
|
||||
log "Устанавливаю конфиг HAProxy..."
|
||||
run_cmd cp -a "$OWGIT_REPO_DIR/haproxy/haproxy.cfg" /etc/haproxy/haproxy.cfg
|
||||
|
||||
# Файлы ACL, чтобы конфиг проходил валидацию.
|
||||
run_cmd touch /etc/haproxy/allowed_ips.lst /etc/haproxy/allowed_ips_knock.lst
|
||||
|
||||
# Если сертификат отсутствует, генерируем временный self-signed.
|
||||
if [[ ! -f /etc/haproxy/certs/papatramp.pem ]]; then
|
||||
warn "Не найден /etc/haproxy/certs/papatramp.pem, создаю временный self-signed сертификат."
|
||||
run_cmd openssl req -x509 -nodes -newkey rsa:2048 \
|
||||
-keyout /tmp/owngit-selfsigned.key \
|
||||
-out /tmp/owngit-selfsigned.crt \
|
||||
-days 7 \
|
||||
-subj "/CN=${SERVER_DOMAIN}"
|
||||
run_sh "cat /tmp/owngit-selfsigned.crt /tmp/owngit-selfsigned.key > /etc/haproxy/certs/papatramp.pem"
|
||||
run_cmd rm -f /tmp/owngit-selfsigned.crt /tmp/owngit-selfsigned.key
|
||||
warn "Используется временный сертификат. Замените на боевой Let's Encrypt/PEM."
|
||||
fi
|
||||
|
||||
run_cmd haproxy -c -f /etc/haproxy/haproxy.cfg
|
||||
run_cmd systemctl enable --now haproxy
|
||||
run_cmd systemctl reload haproxy
|
||||
ok "HAProxy применен и перезагружен"
|
||||
}
|
||||
|
||||
deploy_fail2ban() {
|
||||
if [[ "$ENABLE_FAIL2BAN" != "true" ]]; then
|
||||
warn "Fail2ban пропущен (ENABLE_FAIL2BAN=false)"
|
||||
return
|
||||
fi
|
||||
log "Настраиваю fail2ban..."
|
||||
run_cmd apt-get install -y fail2ban
|
||||
run_cmd mkdir -p /etc/fail2ban/jail.d
|
||||
run_cmd cp -a "$OWGIT_REPO_DIR/fail2ban/jail.d/owngit.local" /etc/fail2ban/jail.d/owngit.local
|
||||
run_cmd systemctl enable --now fail2ban
|
||||
ok "Fail2ban включен"
|
||||
}
|
||||
|
||||
deploy_backup_timer() {
|
||||
if [[ "$ENABLE_BACKUP_TIMER" != "true" ]]; then
|
||||
warn "Backup timer пропущен (ENABLE_BACKUP_TIMER=false)"
|
||||
return
|
||||
fi
|
||||
log "Устанавливаю systemd backup timer..."
|
||||
run_cmd cp -a "$OWGIT_REPO_DIR/systemd/gitea-backup.service" /etc/systemd/system/gitea-backup.service
|
||||
run_cmd cp -a "$OWGIT_REPO_DIR/systemd/gitea-backup.timer" /etc/systemd/system/gitea-backup.timer
|
||||
run_cmd systemctl daemon-reload
|
||||
run_cmd systemctl enable --now gitea-backup.timer
|
||||
ok "Backup timer включен"
|
||||
}
|
||||
|
||||
maybe_setup_ufw() {
|
||||
if [[ "$INSTALL_UFW" != "true" ]]; then
|
||||
warn "UFW не применялся (INSTALL_UFW=false)"
|
||||
return
|
||||
fi
|
||||
if [[ ! -x "$OWGIT_REPO_DIR/scripts/setup-ufw.sh" ]]; then
|
||||
run_cmd chmod +x "$OWGIT_REPO_DIR/scripts/setup-ufw.sh"
|
||||
fi
|
||||
log "Применяю UFW..."
|
||||
run_cmd "$OWGIT_REPO_DIR/scripts/setup-ufw.sh"
|
||||
ok "UFW настроен"
|
||||
}
|
||||
|
||||
print_checklist() {
|
||||
cat <<EOF
|
||||
|
||||
==================== POST-INSTALL CHECKLIST ====================
|
||||
1) Отредактируйте:
|
||||
- ${GITEA_BASE_DIR}/data/gitea/conf/app.ini
|
||||
- замените SECRET_KEY / INTERNAL_TOKEN
|
||||
- проверьте ROOT_URL=https://${SERVER_DOMAIN}/
|
||||
|
||||
2) Проверьте DNS:
|
||||
- ${SERVER_DOMAIN} должен резолвиться на HAProxy (WAN/LAN по вашей схеме).
|
||||
|
||||
3) Замените временный TLS сертификат:
|
||||
- /etc/haproxy/certs/papatramp.pem
|
||||
- и reload: systemctl reload haproxy
|
||||
|
||||
4) Проверки:
|
||||
- curl -I http://${GITEA_LAN_IP}:3000
|
||||
- curl -I https://${SERVER_DOMAIN}
|
||||
- nc -zv ${SERVER_DOMAIN} 2222
|
||||
|
||||
5) Запустите preflight:
|
||||
- chmod +x ${OWGIT_REPO_DIR}/scripts/preflight-check.sh
|
||||
- ${OWGIT_REPO_DIR}/scripts/preflight-check.sh
|
||||
|
||||
6) При необходимости примените SSH hardening:
|
||||
- ${OWGIT_REPO_DIR}/scripts/safe-apply-ssh-hardening.sh
|
||||
================================================================
|
||||
EOF
|
||||
}
|
||||
|
||||
main() {
|
||||
while [[ $# -gt 0 ]]; do
|
||||
case "$1" in
|
||||
--dry-run)
|
||||
DRY_RUN=true
|
||||
shift
|
||||
;;
|
||||
-h|--help)
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
*)
|
||||
err "Неизвестный аргумент: $1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
if [[ "$DRY_RUN" == "true" ]]; then
|
||||
warn "Запуск в режиме dry-run (изменения не применяются)"
|
||||
fi
|
||||
require_root
|
||||
require_paths
|
||||
install_packages
|
||||
setup_dirs
|
||||
deploy_gitea
|
||||
deploy_haproxy
|
||||
deploy_fail2ban
|
||||
deploy_backup_timer
|
||||
maybe_setup_ufw
|
||||
print_checklist
|
||||
ok "Bootstrap завершен"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Массовая миграция репозиториев GitHub -> Gitea.
|
||||
# Переносит всю git-историю через mirror push. Может переносить LFS (опционально).
|
||||
|
||||
: "${GITHUB_TOKEN:?Set GITHUB_TOKEN}"
|
||||
: "${GITHUB_OWNER:?Set GITHUB_OWNER (user or org on GitHub)}"
|
||||
: "${GITHUB_OWNER_TYPE:?Set GITHUB_OWNER_TYPE=user|org}"
|
||||
|
||||
: "${GITEA_URL:?Set GITEA_URL, e.g. https://git.papatramp.ru}"
|
||||
: "${GITEA_TOKEN:?Set GITEA_TOKEN (Gitea PAT)}"
|
||||
: "${GITEA_OWNER:?Set GITEA_OWNER (target user/org in Gitea)}"
|
||||
: "${GITEA_OWNER_TYPE:?Set GITEA_OWNER_TYPE=user|org}"
|
||||
|
||||
WORKDIR="${WORKDIR:-$PWD/migration-workdir}"
|
||||
PUSH_WITH_SSH="${PUSH_WITH_SSH:-true}" # true/false
|
||||
GITEA_SSH_HOST="${GITEA_SSH_HOST:-git.papatramp.ru}"
|
||||
GITEA_SSH_PORT="${GITEA_SSH_PORT:-2222}"
|
||||
MIGRATE_LFS="${MIGRATE_LFS:-false}" # true/false
|
||||
|
||||
mkdir -p "$WORKDIR"
|
||||
|
||||
need_cmd() { command -v "$1" >/dev/null 2>&1 || { echo "Missing command: $1"; exit 1; }; }
|
||||
need_cmd curl
|
||||
need_cmd jq
|
||||
need_cmd git
|
||||
|
||||
if [[ "$MIGRATE_LFS" == "true" ]]; then
|
||||
need_cmd git-lfs
|
||||
fi
|
||||
|
||||
gh_api() {
|
||||
local url="$1"
|
||||
curl -fsSL \
|
||||
-H "Authorization: Bearer $GITHUB_TOKEN" \
|
||||
-H "Accept: application/vnd.github+json" \
|
||||
"$url"
|
||||
}
|
||||
|
||||
gitea_api() {
|
||||
local method="$1"
|
||||
local url="$2"
|
||||
local data="${3:-}"
|
||||
if [[ -n "$data" ]]; then
|
||||
curl -sS -o /tmp/gitea_resp.json -w "%{http_code}" \
|
||||
-X "$method" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "$data" \
|
||||
"$url"
|
||||
else
|
||||
curl -sS -o /tmp/gitea_resp.json -w "%{http_code}" \
|
||||
-X "$method" \
|
||||
-H "Authorization: token $GITEA_TOKEN" \
|
||||
"$url"
|
||||
fi
|
||||
}
|
||||
|
||||
create_gitea_repo_if_needed() {
|
||||
local repo_name="$1"
|
||||
local private="$2"
|
||||
local payload endpoint code
|
||||
|
||||
payload="$(jq -n --arg name "$repo_name" --argjson private "$private" \
|
||||
'{name:$name, private:$private, auto_init:false}')"
|
||||
|
||||
if [[ "$GITEA_OWNER_TYPE" == "org" ]]; then
|
||||
endpoint="$GITEA_URL/api/v1/orgs/$GITEA_OWNER/repos"
|
||||
else
|
||||
endpoint="$GITEA_URL/api/v1/user/repos"
|
||||
fi
|
||||
|
||||
code="$(gitea_api POST "$endpoint" "$payload")"
|
||||
if [[ "$code" == "201" ]]; then
|
||||
echo " [OK] Created: $GITEA_OWNER/$repo_name"
|
||||
elif [[ "$code" == "409" ]]; then
|
||||
echo " [SKIP] Exists: $GITEA_OWNER/$repo_name"
|
||||
else
|
||||
echo " [ERR] Create repo failed: HTTP $code"
|
||||
cat /tmp/gitea_resp.json
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
push_mirror() {
|
||||
local mirror_dir="$1"
|
||||
local repo_name="$2"
|
||||
local remote_url
|
||||
|
||||
if [[ "$PUSH_WITH_SSH" == "true" ]]; then
|
||||
remote_url="ssh://git@${GITEA_SSH_HOST}:${GITEA_SSH_PORT}/${GITEA_OWNER}/${repo_name}.git"
|
||||
else
|
||||
: "${GITEA_USERNAME:?Set GITEA_USERNAME when PUSH_WITH_SSH=false}"
|
||||
remote_url="https://${GITEA_USERNAME}:${GITEA_TOKEN}@${GITEA_URL#https://}/${GITEA_OWNER}/${repo_name}.git"
|
||||
fi
|
||||
|
||||
if git -C "$mirror_dir" remote | grep -q '^gitea$'; then
|
||||
git -C "$mirror_dir" remote set-url gitea "$remote_url"
|
||||
else
|
||||
git -C "$mirror_dir" remote add gitea "$remote_url"
|
||||
fi
|
||||
|
||||
git -C "$mirror_dir" push --mirror gitea
|
||||
}
|
||||
|
||||
migrate_repo() {
|
||||
local full_name="$1"
|
||||
local private="$2"
|
||||
local repo_name="${full_name##*/}"
|
||||
local mirror_dir="$WORKDIR/${repo_name}.git"
|
||||
|
||||
echo "=== $full_name (private=$private) ==="
|
||||
rm -rf "$mirror_dir"
|
||||
|
||||
git -c http.extraHeader="Authorization: Bearer $GITHUB_TOKEN" \
|
||||
clone --mirror "https://github.com/${full_name}.git" "$mirror_dir"
|
||||
|
||||
create_gitea_repo_if_needed "$repo_name" "$private"
|
||||
push_mirror "$mirror_dir" "$repo_name"
|
||||
|
||||
if [[ "$MIGRATE_LFS" == "true" ]]; then
|
||||
git -C "$mirror_dir" lfs fetch --all || true
|
||||
git -C "$mirror_dir" lfs push --all gitea || true
|
||||
fi
|
||||
|
||||
echo " [DONE] $full_name"
|
||||
}
|
||||
|
||||
main() {
|
||||
local page=1
|
||||
local per_page=100
|
||||
local url repos_json count
|
||||
|
||||
while true; do
|
||||
if [[ "$GITHUB_OWNER_TYPE" == "org" ]]; then
|
||||
url="https://api.github.com/orgs/${GITHUB_OWNER}/repos?type=all&per_page=${per_page}&page=${page}"
|
||||
else
|
||||
url="https://api.github.com/users/${GITHUB_OWNER}/repos?type=owner&per_page=${per_page}&page=${page}"
|
||||
fi
|
||||
|
||||
repos_json="$(gh_api "$url")"
|
||||
count="$(echo "$repos_json" | jq 'length')"
|
||||
[[ "$count" -eq 0 ]] && break
|
||||
|
||||
while IFS=$'\t' read -r full_name private; do
|
||||
migrate_repo "$full_name" "$private"
|
||||
done < <(echo "$repos_json" | jq -r '.[] | [.full_name, .private] | @tsv')
|
||||
|
||||
((page++))
|
||||
done
|
||||
|
||||
echo "All repositories migrated."
|
||||
}
|
||||
|
||||
main
|
||||
@@ -0,0 +1,101 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Быстрый preflight для проверки готовности own-git инфраструктуры.
|
||||
# Можно запускать локально или на сервере.
|
||||
|
||||
GIT_DOMAIN="${GIT_DOMAIN:-git.papatramp.ru}"
|
||||
GITEA_LAN_IP="${GITEA_LAN_IP:-192.168.128.100}"
|
||||
GITEA_HTTP_PORT="${GITEA_HTTP_PORT:-3000}"
|
||||
GITEA_SSH_PORT="${GITEA_SSH_PORT:-2222}"
|
||||
HA_PROXY_HTTP_PORT="${HA_PROXY_HTTP_PORT:-80}"
|
||||
HA_PROXY_HTTPS_PORT="${HA_PROXY_HTTPS_PORT:-443}"
|
||||
|
||||
have_cmd() { command -v "$1" >/dev/null 2>&1; }
|
||||
|
||||
check_port() {
|
||||
local host="$1"
|
||||
local port="$2"
|
||||
if have_cmd nc; then
|
||||
nc -z -w 3 "$host" "$port" >/dev/null 2>&1
|
||||
elif have_cmd timeout; then
|
||||
timeout 3 bash -c "cat < /dev/null > /dev/tcp/$host/$port" >/dev/null 2>&1
|
||||
else
|
||||
echo " [WARN] neither nc nor timeout available, skip port test $host:$port"
|
||||
return 0
|
||||
fi
|
||||
}
|
||||
|
||||
ok() { echo "[OK] $*"; }
|
||||
warn() { echo "[WARN] $*"; }
|
||||
err() { echo "[ERR] $*"; }
|
||||
|
||||
echo "== own-git preflight =="
|
||||
echo "GIT_DOMAIN=$GIT_DOMAIN"
|
||||
echo "GITEA_LAN_IP=$GITEA_LAN_IP"
|
||||
echo
|
||||
|
||||
if have_cmd dig; then
|
||||
DNS_RESULT="$(dig +short "$GIT_DOMAIN" | tr '\n' ' ' || true)"
|
||||
if [[ -n "${DNS_RESULT// }" ]]; then
|
||||
ok "DNS resolve: $GIT_DOMAIN -> $DNS_RESULT"
|
||||
else
|
||||
warn "DNS empty for $GIT_DOMAIN"
|
||||
fi
|
||||
elif have_cmd getent; then
|
||||
DNS_RESULT="$(getent hosts "$GIT_DOMAIN" || true)"
|
||||
if [[ -n "$DNS_RESULT" ]]; then
|
||||
ok "DNS resolve via getent: $DNS_RESULT"
|
||||
else
|
||||
warn "DNS empty for $GIT_DOMAIN"
|
||||
fi
|
||||
else
|
||||
warn "no dig/getent, DNS check skipped"
|
||||
fi
|
||||
|
||||
if check_port "$GIT_DOMAIN" "$HA_PROXY_HTTP_PORT"; then
|
||||
ok "Port open: $GIT_DOMAIN:$HA_PROXY_HTTP_PORT"
|
||||
else
|
||||
warn "Port closed/unreachable: $GIT_DOMAIN:$HA_PROXY_HTTP_PORT"
|
||||
fi
|
||||
|
||||
if check_port "$GIT_DOMAIN" "$HA_PROXY_HTTPS_PORT"; then
|
||||
ok "Port open: $GIT_DOMAIN:$HA_PROXY_HTTPS_PORT"
|
||||
else
|
||||
warn "Port closed/unreachable: $GIT_DOMAIN:$HA_PROXY_HTTPS_PORT"
|
||||
fi
|
||||
|
||||
if check_port "$GIT_DOMAIN" "$GITEA_SSH_PORT"; then
|
||||
ok "Port open: $GIT_DOMAIN:$GITEA_SSH_PORT (SSH through HAProxy)"
|
||||
else
|
||||
warn "Port closed/unreachable: $GIT_DOMAIN:$GITEA_SSH_PORT"
|
||||
fi
|
||||
|
||||
if have_cmd curl; then
|
||||
if curl -fsS -m 5 "http://$GITEA_LAN_IP:$GITEA_HTTP_PORT/api/healthz" >/dev/null; then
|
||||
ok "Gitea LAN healthz OK: http://$GITEA_LAN_IP:$GITEA_HTTP_PORT/api/healthz"
|
||||
else
|
||||
warn "Gitea LAN healthz failed: http://$GITEA_LAN_IP:$GITEA_HTTP_PORT/api/healthz"
|
||||
fi
|
||||
|
||||
if curl -fsS -m 8 "https://$GIT_DOMAIN/api/healthz" >/dev/null; then
|
||||
ok "Public healthz OK: https://$GIT_DOMAIN/api/healthz"
|
||||
else
|
||||
warn "Public healthz failed: https://$GIT_DOMAIN/api/healthz"
|
||||
fi
|
||||
else
|
||||
warn "curl not found, HTTP checks skipped"
|
||||
fi
|
||||
|
||||
if have_cmd ssh; then
|
||||
if ssh -o BatchMode=yes -o StrictHostKeyChecking=accept-new -o ConnectTimeout=5 -p "$GITEA_SSH_PORT" "git@$GIT_DOMAIN" exit >/dev/null 2>&1; then
|
||||
ok "SSH handshake OK (auth may still be key-dependent)"
|
||||
else
|
||||
warn "SSH check failed or key not configured: ssh -p $GITEA_SSH_PORT git@$GIT_DOMAIN"
|
||||
fi
|
||||
else
|
||||
warn "ssh not found, SSH check skipped"
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "Preflight complete."
|
||||
@@ -0,0 +1,77 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Восстановление Gitea из архива backup-gitea.sh.
|
||||
#
|
||||
# Пример:
|
||||
# sudo ./scripts/restore-gitea.sh /opt/backups/gitea/gitea-backup-20260421-031000.tar.gz
|
||||
#
|
||||
# По умолчанию:
|
||||
# - target dir: /opt/gitea/data
|
||||
# - create snapshot before restore: yes
|
||||
|
||||
if [[ $# -lt 1 ]]; then
|
||||
echo "Usage: $0 <backup-archive.tar.gz>"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
ARCHIVE_PATH="$1"
|
||||
TARGET_DIR="${TARGET_DIR:-/opt/gitea/data}"
|
||||
TAKE_SNAPSHOT_BEFORE_RESTORE="${TAKE_SNAPSHOT_BEFORE_RESTORE:-true}"
|
||||
SNAPSHOT_DIR="${SNAPSHOT_DIR:-/opt/backups/gitea}"
|
||||
DATE_TAG="$(date +%Y%m%d-%H%M%S)"
|
||||
|
||||
if [[ ! -f "$ARCHIVE_PATH" ]]; then
|
||||
echo "[ERR] Archive not found: $ARCHIVE_PATH"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||
echo "[ERR] Run as root (sudo)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[INFO] Restore archive: $ARCHIVE_PATH"
|
||||
echo "[INFO] Target dir: $TARGET_DIR"
|
||||
|
||||
if [[ "$TAKE_SNAPSHOT_BEFORE_RESTORE" == "true" && -d "$TARGET_DIR" ]]; then
|
||||
mkdir -p "$SNAPSHOT_DIR"
|
||||
SNAPSHOT_PATH="${SNAPSHOT_DIR}/gitea-pre-restore-${DATE_TAG}.tar.gz"
|
||||
echo "[INFO] Creating pre-restore snapshot: $SNAPSHOT_PATH"
|
||||
tar -C "$(dirname "$TARGET_DIR")" -czf "$SNAPSHOT_PATH" "$(basename "$TARGET_DIR")"
|
||||
fi
|
||||
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
if [[ -f /opt/gitea/docker-compose.yml ]]; then
|
||||
echo "[INFO] Stopping Gitea container..."
|
||||
(cd /opt/gitea && docker compose stop gitea)
|
||||
fi
|
||||
fi
|
||||
|
||||
TMP_DIR="$(mktemp -d)"
|
||||
trap 'rm -rf "$TMP_DIR"' EXIT
|
||||
|
||||
echo "[INFO] Extracting archive..."
|
||||
tar -C "$TMP_DIR" -xzf "$ARCHIVE_PATH"
|
||||
|
||||
if [[ ! -d "$TMP_DIR/data" ]]; then
|
||||
echo "[ERR] Archive structure is invalid: expected 'data/' root folder"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[INFO] Restoring files to $TARGET_DIR"
|
||||
rm -rf "$TARGET_DIR"
|
||||
mkdir -p "$(dirname "$TARGET_DIR")"
|
||||
mv "$TMP_DIR/data" "$TARGET_DIR"
|
||||
|
||||
# Чаще всего Gitea в контейнере работает как uid/gid 1000.
|
||||
chown -R 1000:1000 "$TARGET_DIR" || true
|
||||
|
||||
if command -v docker >/dev/null 2>&1; then
|
||||
if [[ -f /opt/gitea/docker-compose.yml ]]; then
|
||||
echo "[INFO] Starting Gitea container..."
|
||||
(cd /opt/gitea && docker compose up -d gitea)
|
||||
fi
|
||||
fi
|
||||
|
||||
echo "[OK] Restore completed"
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Безопасное применение SSH hardening-конфига.
|
||||
# Делает backup текущих конфигов, ставит include-конфиг, валидирует `sshd -t`
|
||||
# и перезагружает ssh только если конфиг валиден.
|
||||
#
|
||||
# ВАЖНО:
|
||||
# - запускать из уже открытой SSH-сессии не страшно, но не закрывайте текущую
|
||||
# сессию, пока не проверите вход новой.
|
||||
|
||||
SOURCE_CONF="${SOURCE_CONF:-ssh/sshd_config.d/90-owngit-hardening.conf}"
|
||||
TARGET_DIR="${TARGET_DIR:-/etc/ssh/sshd_config.d}"
|
||||
TARGET_CONF="${TARGET_DIR}/90-owngit-hardening.conf"
|
||||
BACKUP_DIR="${BACKUP_DIR:-/root/ssh-hardening-backups}"
|
||||
DATE_TAG="$(date +%Y%m%d-%H%M%S)"
|
||||
MGMT_SSH_PORT="${MGMT_SSH_PORT:-22}"
|
||||
|
||||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||
echo "[ERR] Run as root (sudo)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SOURCE_CONF" ]]; then
|
||||
echo "[ERR] Source file not found: $SOURCE_CONF"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v sshd >/dev/null 2>&1; then
|
||||
echo "[ERR] sshd not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
mkdir -p "$TARGET_DIR" "$BACKUP_DIR"
|
||||
|
||||
echo "[INFO] Backing up current ssh config..."
|
||||
if [[ -f /etc/ssh/sshd_config ]]; then
|
||||
cp -a /etc/ssh/sshd_config "${BACKUP_DIR}/sshd_config.${DATE_TAG}.bak"
|
||||
fi
|
||||
if [[ -d /etc/ssh/sshd_config.d ]]; then
|
||||
tar -C /etc/ssh -czf "${BACKUP_DIR}/sshd_config.d.${DATE_TAG}.tar.gz" sshd_config.d
|
||||
fi
|
||||
|
||||
echo "[INFO] Installing hardening config to ${TARGET_CONF}"
|
||||
cp -a "$SOURCE_CONF" "$TARGET_CONF"
|
||||
chmod 0644 "$TARGET_CONF"
|
||||
|
||||
echo "[INFO] Ensuring management SSH port ${MGMT_SSH_PORT} remains allowed in effective config"
|
||||
if ! sshd -T | grep -q "^port ${MGMT_SSH_PORT}$"; then
|
||||
echo "[WARN] Effective sshd port is not ${MGMT_SSH_PORT}. Check your base sshd_config."
|
||||
fi
|
||||
|
||||
echo "[INFO] Validating sshd config..."
|
||||
if sshd -t; then
|
||||
echo "[OK] sshd config is valid"
|
||||
else
|
||||
echo "[ERR] sshd config test failed, restoring previous state"
|
||||
rm -f "$TARGET_CONF"
|
||||
if [[ -f "${BACKUP_DIR}/sshd_config.${DATE_TAG}.bak" ]]; then
|
||||
cp -a "${BACKUP_DIR}/sshd_config.${DATE_TAG}.bak" /etc/ssh/sshd_config
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[INFO] Reloading ssh service..."
|
||||
if systemctl reload ssh; then
|
||||
echo "[OK] ssh reloaded"
|
||||
elif systemctl reload sshd; then
|
||||
echo "[OK] sshd reloaded"
|
||||
else
|
||||
echo "[ERR] failed to reload ssh service"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "[NEXT] Verify from a new terminal before closing current session:"
|
||||
echo " ssh -p ${MGMT_SSH_PORT} <your-user>@<your-server>"
|
||||
echo "If login fails, restore backups from ${BACKUP_DIR}."
|
||||
@@ -0,0 +1,78 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Безопасное применение SSH lockdown-профиля (AllowUsers/Match Address).
|
||||
#
|
||||
# По умолчанию использует шаблон:
|
||||
# ssh/sshd_config.d/95-owngit-lockdown.conf.example
|
||||
# и устанавливает как:
|
||||
# /etc/ssh/sshd_config.d/95-owngit-lockdown.conf
|
||||
#
|
||||
# Перед запуском обязательно отредактируйте пользователей в шаблоне.
|
||||
|
||||
SOURCE_CONF="${SOURCE_CONF:-ssh/sshd_config.d/95-owngit-lockdown.conf.example}"
|
||||
TARGET_DIR="${TARGET_DIR:-/etc/ssh/sshd_config.d}"
|
||||
TARGET_CONF="${TARGET_DIR}/95-owngit-lockdown.conf"
|
||||
BACKUP_DIR="${BACKUP_DIR:-/root/ssh-hardening-backups}"
|
||||
DATE_TAG="$(date +%Y%m%d-%H%M%S)"
|
||||
REQUIRE_ALLOWUSERS="${REQUIRE_ALLOWUSERS:-true}"
|
||||
|
||||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||
echo "[ERR] Run as root (sudo)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ ! -f "$SOURCE_CONF" ]]; then
|
||||
echo "[ERR] Source file not found: $SOURCE_CONF"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v sshd >/dev/null 2>&1; then
|
||||
echo "[ERR] sshd not found."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ "$REQUIRE_ALLOWUSERS" == "true" ]]; then
|
||||
if ! grep -Eq '^[[:space:]]*AllowUsers[[:space:]]+[a-zA-Z0-9._-]+' "$SOURCE_CONF"; then
|
||||
echo "[ERR] SOURCE_CONF must contain non-empty AllowUsers line before apply."
|
||||
echo " Edit: $SOURCE_CONF"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir -p "$TARGET_DIR" "$BACKUP_DIR"
|
||||
|
||||
echo "[INFO] Backing up current sshd include config..."
|
||||
if [[ -f "$TARGET_CONF" ]]; then
|
||||
cp -a "$TARGET_CONF" "${BACKUP_DIR}/95-owngit-lockdown.conf.${DATE_TAG}.bak"
|
||||
fi
|
||||
|
||||
echo "[INFO] Installing lockdown config -> $TARGET_CONF"
|
||||
cp -a "$SOURCE_CONF" "$TARGET_CONF"
|
||||
chmod 0644 "$TARGET_CONF"
|
||||
|
||||
echo "[INFO] Validating sshd config..."
|
||||
if ! sshd -t; then
|
||||
echo "[ERR] sshd -t failed, rolling back"
|
||||
if [[ -f "${BACKUP_DIR}/95-owngit-lockdown.conf.${DATE_TAG}.bak" ]]; then
|
||||
cp -a "${BACKUP_DIR}/95-owngit-lockdown.conf.${DATE_TAG}.bak" "$TARGET_CONF"
|
||||
else
|
||||
rm -f "$TARGET_CONF"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "[INFO] Reloading ssh service..."
|
||||
if systemctl reload ssh; then
|
||||
echo "[OK] ssh reloaded"
|
||||
elif systemctl reload sshd; then
|
||||
echo "[OK] sshd reloaded"
|
||||
else
|
||||
echo "[ERR] failed to reload ssh service"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo
|
||||
echo "[NEXT] Open a NEW terminal and verify access first:"
|
||||
echo " ssh <allowed-user>@<your-server>"
|
||||
echo "If access fails, restore file from $BACKUP_DIR."
|
||||
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# Базовая настройка UFW под схему own-git.
|
||||
# ВНИМАНИЕ: проверьте, что доступ к серверу по SSH есть до включения firewall.
|
||||
|
||||
ALLOW_SSH_PORT="${ALLOW_SSH_PORT:-22}"
|
||||
ALLOW_GITEA_SSH_PORT="${ALLOW_GITEA_SSH_PORT:-2222}"
|
||||
ALLOW_HTTP="${ALLOW_HTTP:-true}"
|
||||
ALLOW_HTTPS="${ALLOW_HTTPS:-true}"
|
||||
ALLOW_SMTP="${ALLOW_SMTP:-true}"
|
||||
ALLOW_RDP="${ALLOW_RDP:-true}"
|
||||
RDP_PORT="${RDP_PORT:-3389}"
|
||||
|
||||
if [[ "${EUID:-$(id -u)}" -ne 0 ]]; then
|
||||
echo "[ERR] Run as root (sudo)."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v ufw >/dev/null 2>&1; then
|
||||
echo "[INFO] Installing ufw..."
|
||||
apt-get update
|
||||
apt-get install -y ufw
|
||||
fi
|
||||
|
||||
echo "[INFO] Reset UFW rules"
|
||||
ufw --force reset
|
||||
|
||||
echo "[INFO] Set default policy"
|
||||
ufw default deny incoming
|
||||
ufw default allow outgoing
|
||||
|
||||
echo "[INFO] Allow SSH management port: ${ALLOW_SSH_PORT}/tcp"
|
||||
ufw allow "${ALLOW_SSH_PORT}/tcp"
|
||||
|
||||
echo "[INFO] Allow Gitea SSH via HAProxy: ${ALLOW_GITEA_SSH_PORT}/tcp"
|
||||
ufw allow "${ALLOW_GITEA_SSH_PORT}/tcp"
|
||||
|
||||
if [[ "$ALLOW_HTTP" == "true" ]]; then
|
||||
ufw allow 80/tcp
|
||||
fi
|
||||
|
||||
if [[ "$ALLOW_HTTPS" == "true" ]]; then
|
||||
ufw allow 443/tcp
|
||||
fi
|
||||
|
||||
if [[ "$ALLOW_SMTP" == "true" ]]; then
|
||||
ufw allow 25/tcp
|
||||
fi
|
||||
|
||||
if [[ "$ALLOW_RDP" == "true" ]]; then
|
||||
ufw allow "${RDP_PORT}/tcp"
|
||||
fi
|
||||
|
||||
echo "[INFO] Enable UFW"
|
||||
ufw --force enable
|
||||
|
||||
echo "[OK] UFW configured"
|
||||
ufw status verbose
|
||||
@@ -0,0 +1,26 @@
|
||||
# OwnGit SSH hardening baseline.
|
||||
# Этот файл рассчитан на OpenSSH в Ubuntu 24.04 (sshd_config.d include).
|
||||
|
||||
# Запрет логина root по SSH.
|
||||
PermitRootLogin no
|
||||
|
||||
# Только ключевая аутентификация.
|
||||
PasswordAuthentication no
|
||||
KbdInteractiveAuthentication no
|
||||
PubkeyAuthentication yes
|
||||
|
||||
# Ограничение попыток.
|
||||
MaxAuthTries 4
|
||||
LoginGraceTime 30
|
||||
|
||||
# Не принимать пустые пароли.
|
||||
PermitEmptyPasswords no
|
||||
|
||||
# Ограничить пробросы по умолчанию.
|
||||
AllowTcpForwarding no
|
||||
X11Forwarding no
|
||||
AllowAgentForwarding no
|
||||
|
||||
# Keepalive для очистки "висящих" соединений.
|
||||
ClientAliveInterval 300
|
||||
ClientAliveCountMax 2
|
||||
@@ -0,0 +1,23 @@
|
||||
# Optional SSH lockdown profile (EXAMPLE).
|
||||
# Скопируйте в /etc/ssh/sshd_config.d/95-owngit-lockdown.conf и адаптируйте под себя.
|
||||
#
|
||||
# ВНИМАНИЕ:
|
||||
# - сначала проверьте, что у перечисленных пользователей есть рабочие SSH-ключи;
|
||||
# - применяйте через scripts/safe-apply-ssh-lockdown.sh;
|
||||
# - не закрывайте текущую сессию, пока не проверите вход новой.
|
||||
|
||||
# Разрешить вход только выбранным пользователям.
|
||||
AllowUsers youradmin anotheradmin
|
||||
|
||||
# Опционально: ограничить, в каких сетях разрешен вход.
|
||||
# Пример: разрешить admin только из домашней подсети.
|
||||
# Match Address 192.168.128.0/24
|
||||
# AllowUsers youradmin
|
||||
|
||||
# Пример: явный запрет SSH с публичного интернета.
|
||||
# Match Address 0.0.0.0/0
|
||||
# DenyUsers *
|
||||
|
||||
# Дополнительное ужесточение:
|
||||
PermitTunnel no
|
||||
GatewayPorts no
|
||||
@@ -0,0 +1,13 @@
|
||||
[Unit]
|
||||
Description=Run Gitea backup script
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
Type=oneshot
|
||||
ExecStart=/opt/owngit/scripts/backup-gitea.sh
|
||||
User=root
|
||||
Group=root
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
@@ -0,0 +1,11 @@
|
||||
[Unit]
|
||||
Description=Daily Gitea backup timer
|
||||
|
||||
[Timer]
|
||||
OnCalendar=*-*-* 03:10:00
|
||||
Persistent=true
|
||||
RandomizedDelaySec=3m
|
||||
Unit=gitea-backup.service
|
||||
|
||||
[Install]
|
||||
WantedBy=timers.target
|
||||
Reference in New Issue
Block a user