2505d91d4b
Made-with: Cursor
375 lines
14 KiB
Bash
375 lines
14 KiB
Bash
#!/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) Проверки:
|
||
- curl -I http://${GITEA_LAN_IP}:3000
|
||
- после обновления внешнего HAProxy: curl -I https://${SERVER_DOMAIN}
|
||
- после обновления внешнего 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 "$@"
|