Initial own-git docs, configs, scripts, and hardening pack

This commit is contained in:
PTah
2026-04-21 21:37:43 +10:00
commit f5899ebc56
25 changed files with 1843 additions and 0 deletions
+48
View File
@@ -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"
+246
View File
@@ -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 "$@"
+156
View File
@@ -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
+101
View File
@@ -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."
+77
View File
@@ -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"
+78
View File
@@ -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}."
+78
View File
@@ -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."
+59
View File
@@ -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