diff --git a/bin/zarya b/bin/zarya index 5ce1031..ad0d358 100755 --- a/bin/zarya +++ b/bin/zarya @@ -26,7 +26,7 @@ case "${SUBCMD}" in pack) exec "${ZARYA_DIR}/cmd/pack.sh" "$@" ;; reset|nuke) exec "${ZARYA_DIR}/layers/reset.sh" "$@" ;; gc) exec "${ZARYA_DIR}/layers/gc.sh" "$@" ;; - srv-init|vps-init) exec "${ZARYA_DIR}/cmd/vps-init.sh" "$@" ;; + init) exec "${ZARYA_DIR}/cmd/init.sh" "$@" ;; help|--help|-h) cat < Плейбуки Ansible (01, 02) z a [-auto-approve] OpenTofu apply в RAM-изоляции - z srv-init [ALIAS] [FLAGS] Day-0 удаленный бутстрап ноды .srv в 1 клик + z init [аргументы...] Модульный Day-0 бутстрап (vps) Телеметрия и LLM: z snap Снятие аудита в state/snapshots/ z pack [all|atlas|code|state] Сборка контекстов Repomix для LLM diff --git a/cmd/init.sh b/cmd/init.sh new file mode 100755 index 0000000..5417741 --- /dev/null +++ b/cmd/init.sh @@ -0,0 +1,23 @@ +#!/bin/bash +set -euo pipefail +ZARYA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +source "${ZARYA_DIR}/lib/common.sh" + +TARGET="${1:-}" +shift || true + +case "${TARGET}" in + vps|srv) + exec "${ZARYA_DIR}/cmd/init/vps.sh" "$@" + ;; + help|--help|-h|"") + echo "Использование: z init [аргументы...]" + echo "Доступные цели инициализации:" + echo " vps Day-0 удаленный бутстрап ноды .srv" + exit 0 + ;; + *) + log_err "Неизвестная цель инициализации: ${TARGET}. Доступно: vps" + exit 1 + ;; +esac \ No newline at end of file diff --git a/cmd/init/vps.sh b/cmd/init/vps.sh new file mode 100755 index 0000000..6146633 --- /dev/null +++ b/cmd/init/vps.sh @@ -0,0 +1,123 @@ +#!/bin/bash +set -euo pipefail +ZARYA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +source "${ZARYA_DIR}/lib/common.sh" + +TARGET_IP="" +ALIAS="" +PORT="${SRV_DEFAULT_PORT:-1488}" +USER_NAME="${SRV_DEFAULT_USER:-atlas}" +TCP_PORTS="${SRV_DEFAULT_TCP_PORTS:-1488 80 443}" +UDP_PORTS="${SRV_DEFAULT_UDP_PORTS:-51820}" +UFW_IN="${SRV_UFW_INCOMING:-deny}" +UFW_OUT="${SRV_UFW_OUTGOING:-allow}" +UFW_FWD="${SRV_UFW_FORWARD:-deny}" +UFW_APP="${SRV_UFW_APPLICATION:-skip}" +ENABLE_BBR="${SRV_ENABLE_BBR:-true}" +DISABLE_IPV6="${SRV_DISABLE_IPV6:-true}" +DISABLE_ICMP="${SRV_DISABLE_ICMP:-false}" +VERIFY_GATE="false" + +PUBKEY="ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGY8TEByHoDFNsy18VG6BJALc8HUYeoKZz2PeKMyKMND atlas@core" + +while [[ $# -gt 0 ]]; do + case "$1" in + --verify|--test|-v) + VERIFY_GATE="true" + shift + ;; + --port) + PORT="$2" + shift 2 + ;; + --user) + USER_NAME="$2" + shift 2 + ;; + --tcp) + TCP_PORTS="$2" + shift 2 + ;; + --udp) + UDP_PORTS="$2" + shift 2 + ;; + --disable-icmp) + DISABLE_ICMP="true" + shift + ;; + --enable-icmp) + DISABLE_ICMP="false" + shift + ;; + --disable-ipv6) + DISABLE_IPV6="true" + shift + ;; + --enable-ipv6) + DISABLE_IPV6="false" + shift + ;; + --no-bbr) + ENABLE_BBR="false" + shift + ;; + --help|-h) + echo "Использование: z init vps [ALIAS] [--verify] [--port N] [--user NAME] [--tcp 'PORTS'] [--udp 'PORTS'] [--disable-icmp] [--disable-ipv6] [--no-bbr]" + exit 0 + ;; + *) + if [[ -z "${TARGET_IP}" ]]; then + TARGET_IP="$1" + elif [[ -z "${ALIAS}" ]]; then + ALIAS="$1" + fi + shift + ;; + esac +done + +if [[ -z "${TARGET_IP}" ]]; then + log_err "IP-адрес целевого сервера обязателен. Введите 'z init vps --help'." + exit 1 +fi + +if [[ -z "${ALIAS}" ]]; then + LAST_OCTET="${TARGET_IP##*.}" + ALIAS="n${LAST_OCTET}.srv" +fi + +log_info "Инициализация ноды ${ALIAS} (${TARGET_IP}:${PORT})..." + +ssh -o StrictHostKeyChecking=accept-new -p 22 "root@${TARGET_IP}" \ + "env BOOTSTRAP_USER='${USER_NAME}' \ + BOOTSTRAP_PORT='${PORT}' \ + BOOTSTRAP_PUBKEY='${PUBKEY}' \ + BOOTSTRAP_TCP_PORTS='${TCP_PORTS}' \ + BOOTSTRAP_UDP_PORTS='${UDP_PORTS}' \ + BOOTSTRAP_UFW_IN='${UFW_IN}' \ + BOOTSTRAP_UFW_OUT='${UFW_OUT}' \ + BOOTSTRAP_UFW_FWD='${UFW_FWD}' \ + BOOTSTRAP_UFW_APP='${UFW_APP}' \ + BOOTSTRAP_ENABLE_BBR='${ENABLE_BBR}' \ + BOOTSTRAP_DISABLE_IPV6='${DISABLE_IPV6}' \ + BOOTSTRAP_DISABLE_ICMP='${DISABLE_ICMP}' \ + bash -s" < "${ZARYA_DIR}/templates/vps-bootstrap.sh" + +ssh_register_node "${ALIAS}" "${TARGET_IP}" + +if [[ "${VERIFY_GATE}" == "true" ]]; then + log_warn "Включен режим ручного подтверждения." + echo "Откройте соседний терминал и выполните: ssh ${ALIAS}" + read -r -p "Подключение успешно? [y/N]: " CONFIRM + if [[ "${CONFIRM}" != "y" && "${CONFIRM}" != "Y" ]]; then + log_err "Подтверждение отклонено оператором. Откат конфигурации..." + ssh -o ConnectTimeout=10 -p "${PORT}" -i "${RAM_KEY}" "${USER_NAME}@${TARGET_IP}" "sudo ufw disable && sudo sed -i -E 's/^Port .*/Port 22/' /etc/ssh/sshd_config && sudo systemctl restart ssh.service" || true + log_warn "Откат завершен: порт 22 возвращен, UFW выключен." + exit 1 + fi +fi + +log_info "Автоматическая валидация ноды ${ALIAS}..." +ssh -o ConnectTimeout=5 "${ALIAS}" "hostname" +log_ok "Сервер ${ALIAS} успешно инициализирован и верифицирован." diff --git a/lib/common.sh b/lib/common.sh index be995bf..dcc8add 100755 --- a/lib/common.sh +++ b/lib/common.sh @@ -3,9 +3,9 @@ set -euo pipefail ZARYA_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" export ATLAS_DIR="${ATLAS_DIR:-$HOME/Projects/atlas}" export REPO_ROOT="${ATLAS_DIR}" - source "${ZARYA_DIR}/config.env" 2>/dev/null || true source "${ZARYA_DIR}/lib/log.sh" source "${ZARYA_DIR}/lib/net.sh" source "${ZARYA_DIR}/lib/vault.sh" -source "${ZARYA_DIR}/lib/session.sh" \ No newline at end of file +source "${ZARYA_DIR}/lib/session.sh" +source "${ZARYA_DIR}/lib/ssh.sh" \ No newline at end of file diff --git a/lib/ssh.sh b/lib/ssh.sh new file mode 100644 index 0000000..495f3b3 --- /dev/null +++ b/lib/ssh.sh @@ -0,0 +1,17 @@ +#!/bin/bash +set -euo pipefail + +ssh_register_node() { + local alias="$1" + local ip="$2" + local config_file="${HOME}/.ssh/config" + + if [[ ! -f "${config_file}" ]]; then + touch "${config_file}" + chmod 600 "${config_file}" + fi + + if ! grep -q "Host.*[[:space:]]${alias}\([[:space:]]\|$\)" "${config_file}" 2>/dev/null; then + printf "\nHost %s\n Hostname %s\n" "${alias}" "${ip}" >> "${config_file}" + fi +} \ No newline at end of file diff --git a/templates/vps-bootstrap.sh b/templates/vps-bootstrap.sh new file mode 100755 index 0000000..e68878d --- /dev/null +++ b/templates/vps-bootstrap.sh @@ -0,0 +1,82 @@ +#!/bin/bash +set -euo pipefail + +USER_NAME="${BOOTSTRAP_USER:-atlas}" +PORT="${BOOTSTRAP_PORT:-1488}" +PUBKEY="${BOOTSTRAP_PUBKEY}" +TCP_PORTS="${BOOTSTRAP_TCP_PORTS:-1488 80 443}" +UDP_PORTS="${BOOTSTRAP_UDP_PORTS:-51820}" +UFW_IN="${BOOTSTRAP_UFW_IN:-deny}" +UFW_OUT="${BOOTSTRAP_UFW_OUT:-allow}" +UFW_FWD="${BOOTSTRAP_UFW_FWD:-deny}" +UFW_APP="${BOOTSTRAP_UFW_APP:-skip}" +ENABLE_BBR="${BOOTSTRAP_ENABLE_BBR:-true}" +DISABLE_IPV6="${BOOTSTRAP_DISABLE_IPV6:-true}" +DISABLE_ICMP="${BOOTSTRAP_DISABLE_ICMP:-false}" + +id -u "${USER_NAME}" &>/dev/null || useradd -m -s /bin/bash -G sudo "${USER_NAME}" +mkdir -p "/home/${USER_NAME}/.ssh" +echo "${PUBKEY}" > "/home/${USER_NAME}/.ssh/authorized_keys" +chmod 700 "/home/${USER_NAME}/.ssh" +chmod 600 "/home/${USER_NAME}/.ssh/authorized_keys" +chown -R "${USER_NAME}:${USER_NAME}" "/home/${USER_NAME}/.ssh" + +echo "${USER_NAME} ALL=(ALL) NOPASSWD:ALL" > "/etc/sudoers.d/${USER_NAME}" +chmod 440 "/etc/sudoers.d/${USER_NAME}" + +if systemctl is-active --quiet ssh.socket 2>/dev/null; then + systemctl disable --now ssh.socket +fi + +rm -rf /etc/ssh/sshd_config.d/*.conf +cat << SSHEOF > /etc/ssh/sshd_config +Port ${PORT} +PasswordAuthentication no +KbdInteractiveAuthentication no +PermitRootLogin prohibit-password +PubkeyAuthentication yes +X11Forwarding no +AcceptEnv LANG LC_* +Subsystem sftp /usr/lib/openssh/sftp-server +SSHEOF + +systemctl enable --now ssh.service +systemctl restart ssh.service + +which ufw >/dev/null 2>&1 || (DEBIAN_FRONTEND=noninteractive apt-get update -y && DEBIAN_FRONTEND=noninteractive apt-get install -y ufw) +ufw --force reset +ufw default ${UFW_IN} incoming +ufw default ${UFW_OUT} outgoing +ufw default ${UFW_FWD} forward +sed -i -E 's/^DEFAULT_APPLICATION_POLICY=.*/DEFAULT_APPLICATION_POLICY="'${UFW_APP}'"/' /etc/default/ufw || true + +if [[ "${DISABLE_IPV6}" == "true" ]]; then + sed -i -E 's/^IPV6=.*/IPV6=no/' /etc/default/ufw || true +fi + +for p in ${TCP_PORTS}; do + ufw allow "${p}/tcp" +done + +for p in ${UDP_PORTS}; do + ufw allow "${p}/udp" +done + +ufw --force enable + +rm -f /etc/sysctl.d/99-atlas.conf +touch /etc/sysctl.d/99-atlas.conf + +if [[ "${ENABLE_BBR}" == "true" ]]; then + printf "net.core.default_qdisc = fq\nnet.ipv4.tcp_congestion_control = bbr\n" >> /etc/sysctl.d/99-atlas.conf +fi + +if [[ "${DISABLE_IPV6}" == "true" ]]; then + printf "net.ipv6.conf.all.disable_ipv6 = 1\nnet.ipv6.conf.default.disable_ipv6 = 1\nnet.ipv6.conf.lo.disable_ipv6 = 1\n" >> /etc/sysctl.d/99-atlas.conf +fi + +if [[ "${DISABLE_ICMP}" == "true" ]]; then + printf "net.ipv4.icmp_echo_ignore_all = 1\n" >> /etc/sysctl.d/99-atlas.conf +fi + +sysctl --system >/dev/null 2>&1 || true \ No newline at end of file