Files
owngit/scripts/bootstrap-own-git.sh
T
2026-04-22 12:37:53 +10:00

375 lines
14 KiB
Bash
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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).
#
# OWGIT_REPO_DIR по умолчанию — корень репозитория (каталог над scripts/). Для установки из /opt/owngit задайте явно:
# cd /opt/owngit && sudo OWGIT_REPO_DIR="$(pwd)" ./scripts/bootstrap-own-git.sh
_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
_REPO_ROOT="$(cd "${_SCRIPT_DIR}/.." && pwd)"
OWGIT_REPO_DIR="${OWGIT_REPO_DIR:-${_REPO_ROOT}}"
SERVER_DOMAIN="${SERVER_DOMAIN:-git.papatramp.ru}"
GITEA_LAN_IP="${GITEA_LAN_IP:-192.168.128.100}"
GITEA_BASE_DIR="${GITEA_BASE_DIR:-/opt/gitea}"
ENABLE_HAPROXY="${ENABLE_HAPROXY:-false}"
ENABLE_REMOTE_HAPROXY_DEPLOY="${ENABLE_REMOTE_HAPROXY_DEPLOY:-false}"
REMOTE_HAPROXY_HOST="${REMOTE_HAPROXY_HOST:-192.168.128.65}"
REMOTE_HAPROXY_PORT="${REMOTE_HAPROXY_PORT:-22}"
REMOTE_HAPROXY_USER="${REMOTE_HAPROXY_USER:-papatramp}"
REMOTE_HAPROXY_PASSWORD="${REMOTE_HAPROXY_PASSWORD:-}"
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 показать эту справку
Переменные окружения (основные):
OWGIT_REPO_DIR каталог с репозиторием owngit (по умолчанию: родитель scripts/)
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
local required_paths=(
"$OWGIT_REPO_DIR/gitea/docker-compose.yml" \
"$OWGIT_REPO_DIR/gitea/app.ini.example" \
"$OWGIT_REPO_DIR/systemd/gitea-backup.service" \
"$OWGIT_REPO_DIR/systemd/gitea-backup.timer" \
"$OWGIT_REPO_DIR/fail2ban/jail.d/owngit.local"
)
if [[ "$ENABLE_HAPROXY" == "true" || "$ENABLE_REMOTE_HAPROXY_DEPLOY" == "true" ]]; then
required_paths+=("$OWGIT_REPO_DIR/haproxy/haproxy.cfg")
fi
for p in "${required_paths[@]}"; do
if [[ ! -f "$p" ]]; then
err "Не найден файл: $p"
missing=1
fi
done
if [[ "$missing" -ne 0 ]]; then
err "OWGIT_REPO_DIR=${OWGIT_REPO_DIR}"
err "Должен указывать на корень клона owngit (рядом должны быть каталоги gitea/, systemd/, fail2ban/)."
warn "Если клон не в этой папке: sudo OWGIT_REPO_DIR=/путь/к/owngit ENABLE_HAPROXY=false ./scripts/bootstrap-own-git.sh"
warn "Из каталога клона без переменной (автовыбор каталога): cd /путь/к/owngit && sudo ENABLE_HAPROXY=false ./scripts/bootstrap-own-git.sh"
warn "Если нужен именно /opt/owngit — клонируйте репозиторий туда или скопируйте дерево файлов."
exit 1
fi
}
install_packages() {
log "Устанавливаю зависимости..."
run_cmd apt-get update
local packages=(
ca-certificates curl jq git openssl openssh-client
)
if [[ "$ENABLE_HAPROXY" == "true" ]]; then
packages+=(haproxy)
fi
if [[ "$ENABLE_REMOTE_HAPROXY_DEPLOY" == "true" && -n "$REMOTE_HAPROXY_PASSWORD" ]]; then
packages+=(sshpass)
fi
run_cmd apt-get install -y "${packages[@]}"
bootstrap_install_docker
ok "Пакеты установлены"
}
# Docker: не трогаем apt, если уже есть рабочий «docker compose» (избегаем лишних транзакций и конфликтов).
# Ошибка containerd vs containerd.io — см. docs/troubleshooting.md §12.
bootstrap_install_docker() {
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY] apt-get install -y docker.io docker-compose-plugin"
return 0
fi
if command -v docker >/dev/null 2>&1 && docker compose version >/dev/null 2>&1; then
ok "Docker и «docker compose» уже доступны — не устанавливаю docker.io/docker-compose-plugin"
return 0
fi
log "Устанавливаю docker.io и docker-compose-plugin..."
if apt-get install -y docker.io docker-compose-plugin; then
return 0
fi
err "Не удалось установить docker.io и docker-compose-plugin."
err "Частая причина: конфликт пакетов containerd (Ubuntu, от docker.io) и containerd.io (Docker Inc)."
warn "Оставьте один стек Docker: см. docs/troubleshooting.md — «12) containerd.io Conflicts: containerd»."
exit 1
}
setup_dirs() {
log "Создаю рабочие директории..."
run_cmd mkdir -p "$GITEA_BASE_DIR/data/gitea/conf"
if [[ "$ENABLE_HAPROXY" == "true" ]]; then
run_cmd mkdir -p /etc/haproxy/certs
fi
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() {
if [[ "$ENABLE_HAPROXY" != "true" ]]; then
warn "HAProxy пропущен (ENABLE_HAPROXY=false). Используйте существующий внешний HAProxy."
return
fi
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 применен и перезагружен"
}
remote_ssh_cmd() {
local cmd="$1"
local ssh_target="${REMOTE_HAPROXY_USER}@${REMOTE_HAPROXY_HOST}"
local ssh_opts=(-p "$REMOTE_HAPROXY_PORT" -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10)
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY] ssh ${ssh_target} ${cmd}"
return 0
fi
if [[ -n "$REMOTE_HAPROXY_PASSWORD" ]]; then
SSHPASS="$REMOTE_HAPROXY_PASSWORD" sshpass -e ssh "${ssh_opts[@]}" "$ssh_target" "$cmd"
else
ssh "${ssh_opts[@]}" "$ssh_target" "$cmd"
fi
}
remote_scp_upload() {
local src="$1"
local dst="$2"
local ssh_target="${REMOTE_HAPROXY_USER}@${REMOTE_HAPROXY_HOST}"
local scp_opts=(-P "$REMOTE_HAPROXY_PORT" -o StrictHostKeyChecking=accept-new -o ConnectTimeout=10)
if [[ "$DRY_RUN" == "true" ]]; then
echo "[DRY] scp ${src} ${ssh_target}:${dst}"
return 0
fi
if [[ -n "$REMOTE_HAPROXY_PASSWORD" ]]; then
SSHPASS="$REMOTE_HAPROXY_PASSWORD" sshpass -e scp "${scp_opts[@]}" "$src" "${ssh_target}:${dst}"
else
scp "${scp_opts[@]}" "$src" "${ssh_target}:${dst}"
fi
}
deploy_remote_haproxy() {
if [[ "$ENABLE_REMOTE_HAPROXY_DEPLOY" != "true" ]]; then
warn "Удаленный деплой HAProxy пропущен (ENABLE_REMOTE_HAPROXY_DEPLOY=false)"
return
fi
log "Обновляю HAProxy удаленно на ${REMOTE_HAPROXY_HOST}:${REMOTE_HAPROXY_PORT}..."
local ts backup_cfg remote_tmp
ts="$(date +%Y%m%d-%H%M%S)"
backup_cfg="/etc/haproxy/haproxy.cfg.bak.${ts}"
remote_tmp="/tmp/owngit-haproxy.cfg.${ts}"
remote_ssh_cmd "sudo cp -a /etc/haproxy/haproxy.cfg '${backup_cfg}'"
remote_scp_upload "$OWGIT_REPO_DIR/haproxy/haproxy.cfg" "$remote_tmp"
remote_ssh_cmd "sudo mv '${remote_tmp}' /etc/haproxy/haproxy.cfg && sudo chown root:root /etc/haproxy/haproxy.cfg && sudo chmod 644 /etc/haproxy/haproxy.cfg"
remote_ssh_cmd "sudo touch /etc/haproxy/allowed_ips.lst /etc/haproxy/allowed_ips_knock.lst"
if remote_ssh_cmd "sudo haproxy -c -f /etc/haproxy/haproxy.cfg"; then
remote_ssh_cmd "sudo systemctl reload haproxy"
ok "Удаленный HAProxy обновлен, backup: ${backup_cfg}"
else
warn "Валидация удаленного конфига не прошла, выполняю откат..."
remote_ssh_cmd "sudo cp -a '${backup_cfg}' /etc/haproxy/haproxy.cfg"
remote_ssh_cmd "sudo haproxy -c -f /etc/haproxy/haproxy.cfg && sudo systemctl reload haproxy"
err "Откат выполнен. Проверьте haproxy/haproxy.cfg перед повтором."
exit 1
fi
}
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() {
local haproxy_host_hint
if [[ "$ENABLE_HAPROXY" == "true" ]]; then
haproxy_host_hint="локальном сервере"
else
haproxy_host_hint="внешнем HAProxy"
fi
cat <<EOF
==================== POST-INSTALL CHECKLIST ====================
1) Отредактируйте ${GITEA_BASE_DIR}/data/gitea/conf/app.ini:
- секция [security]: SECRET_KEY и INTERNAL_TOKEN — длинные случайные строки (не оставляйте CHANGE_ME_*).
Сгенерировать: два раза openssl rand -hex 32, подставить в SECRET_KEY и INTERNAL_TOKEN.
- проверьте ROOT_URL=https://${SERVER_DOMAIN}/
2) Проверьте DNS:
- ${SERVER_DOMAIN} должен резолвиться на HAProxy (WAN/LAN по вашей схеме).
3) HAProxy/TLS:
- если ENABLE_HAPROXY=true: замените /etc/haproxy/certs/papatramp.pem и выполните systemctl reload haproxy
- если ENABLE_HAPROXY=false: обновите backend/SSH-proxy на ${haproxy_host_hint}
- если ENABLE_REMOTE_HAPROXY_DEPLOY=true: удаленный /etc/haproxy/haproxy.cfg уже обновлен с backup/rollback
4) Проверки:
- Gitea локально: curl -sS -o /dev/null -w "%{http_code}\\n" http://${GITEA_LAN_IP}:3000/api/healthz (ожидайте 200; не curl -I на корень — будет 405)
- после обновления внешнего HAProxy: curl -sSI https://${SERVER_DOMAIN}/api/healthz | head -n1
- после обновления внешнего HAProxy: 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 (изменения не применяются)"
log "OWGIT_REPO_DIR=${OWGIT_REPO_DIR}"
fi
require_root
require_paths
install_packages
setup_dirs
deploy_gitea
deploy_haproxy
deploy_remote_haproxy
deploy_fail2ban
deploy_backup_timer
maybe_setup_ufw
print_checklist
ok "Bootstrap завершен"
}
main "$@"