diff --git a/.gitignore b/.gitignore index b76daa0..b7fd586 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ /public/ +.venv/ .DS_Store Thumbs.db diff --git a/DEPLOY_INSTRUCTIONS.md b/DEPLOY_INSTRUCTIONS.md index 27095e1..c2c5cb3 100644 --- a/DEPLOY_INSTRUCTIONS.md +++ b/DEPLOY_INSTRUCTIONS.md @@ -2,29 +2,36 @@ ## Описание -Этот документ описывает процесс публикации сгенерированного сайта (папка `public`) на хостинге через SFTP. Решение кроссплатформенное и работает как на Unix-подобных системах (Linux/macOS), так и на Windows. +Этот документ описывает процесс публикации сгенерированного сайта (папка `public`) +на хостинге через SFTP. Решение кроссплатформенное и работает как на Unix-подобных +системах (Linux/macOS), так и на Windows. + +> **ВАЖНО: деплой сайта выполняется ТОЛЬКО через `make deploy`.** Аккаунт Jino — +> SFTP-only: удалённый запуск команд (rsync, ssh-exec) запрещён сервером +> (ошибка `exec request failed on channel 0`). Прямой rsync на этот аккаунт +> НЕ РАБОТАЕТ. ## Учетные данные и параметры подключения -Для работы используются следующие параметры: - - **Хост**: kpa39l.myjino.ru - **Порт SFTP**: 2222 - **Пользователь**: kpa39l_dedinit - **Пароль**: -Zp4ep747t25 -- **Целевой путь**: /dedinit.ru (относительно домашнего каталога пользователя) +- **Целевой путь**: /dedinit.ru (относительно корня аккаунта) ## Установка зависимостей ### Для Linux/macOS -- **rsync** - установите через менеджер пакетов (например, apt-get install rsync или brew install rsync) -- **Make** - для выполнения команд из Makefile +- **Python 3** (с venv/pip) +- **Make** — для выполнения команд из Makefile +- Пакет `paramiko` ставится автоматически в `.venv/` при первом `make deploy` + (таргет `venv`), либо вручную: `pip install paramiko` ### Для Windows -- **WinSCP** - установите с официального сайта () -- **Make** - можно установить через Chocolatey, MinGW или Git for Windows +- **WinSCP** — установите с официального сайта () +- **Make** — можно установить через Chocolatey, MinGW или Git for Windows ## Использование Makefile @@ -39,23 +46,28 @@ make build ### Деплой сайта ```bash +# один раз экспортируем пароль SFTP (каждый раз в новой сессии) +export SSHPASS='-Zp4ep747t25' make deploy ``` -Команда сначала собирает сайт, затем загружает содержимое папки `public/` на удаленный сервер. +ЕДИНСТВЕННЫЙ способ публикации. Сначала собирает сайт, затем зеркалирует +`public/` на сервер (`/dedinit.ru`) с удалением устаревших файлов +(аналог `rsync --delete`): -Для Windows используется WinSCP.com для SFTP-подключения, для Unix-подобных систем - rsync. +- **Unix**: `tools/deploy_sftp.py` (paramiko, SFTP + рекурсивный mirror) +- **Windows**: WinSCP.com (скрипт `winscp_script.txt`) + +Если `SSHPASS` не задана — `make deploy` завершится с ошибкой и подсказкой. ### Симуляция деплоя ```bash +export SSHPASS='-Zp4ep747t25' make deploy-dry-run ``` -Команда сначала собирает сайт, затем симулирует загрузку на сервер: - -- На Unix-подобных системах используется флаг `-n` для симуляции rsync -- На Windows симуляция пока не реализована, будет выведено соответствующее сообщение +Собирает сайт и показывает, какие файлы будут загружены/удалены, без заливки. ### Очистка @@ -83,18 +95,15 @@ make help ## Конфигурация -Параметры подключения находятся в файле Makefile: +Параметры подключения для Unix находятся в `tools/deploy_sftp.py` +(переменные `DEDINIT_HOST`, `DEDINIT_PORT`, `DEDINIT_USER`, `DEDINIT_REMOTE`, +по умолчанию — kpa39l.myjino.ru:2222 / kpa39l_dedinit / /dedinit.ru). +На Windows — в `Makefile` и `winscp_script.txt`. -- HOST - адрес сервера -- PORT - порт SFTP -- USERNAME - имя пользователя -- REMOTE_PATH - путь на удаленном сервере (относительно домашнего каталога пользователя) - -При необходимости вы можете изменить эти параметры в файле Makefile. +При необходимости вы можете изменить эти параметры. ## Настройка для Windows -Для корректной работы на Windows необходимо: - -1. Убедиться, что WinSCP установлен в стандартную директорию `C:\Program Files (x86)\WinSCP\` -2. Файл `winscp_script.txt` содержит команды для подключения и синхронизации файлов +1. Убедиться, что WinSCP установлен в стандартную директорию + `C:\Program Files (x86)\WinSCP\` +2. Файл `winscp_script.txt` содержит команды для подключения и синхронизации файлов \ No newline at end of file diff --git a/Makefile b/Makefile index 40c1ae6..2e2ac69 100644 --- a/Makefile +++ b/Makefile @@ -7,35 +7,48 @@ ifeq ($(OS),Windows_NT) DRYRUN_CMD = echo "Windows: проверка SFTP-соединения..." else PLATFORM = Unix - # На Unix-подобных системах используем rsync для SFTP - HOST = kpa39l.myjino.ru - PORT = 2222 - USERNAME = kpa39l_dedinit - REMOTE_PATH = /dedinit.ru + # На Unix используем paramiko (SFTP). Аккаунт Jino — SFTP-only: rsync/ssh-exec + # запрещены ("exec request failed on channel 0"), поэтому deploy через + # tools/deploy_sftp.py (рекурсивный mirror + удаление лишнего). + # Венв .venv создаётся автоматически таргетом venv (нужен paramiko). + DEPLOY_CMD = .venv/bin/python tools/deploy_sftp.py + DRYRUN_CMD = .venv/bin/python tools/deploy_sftp.py --dry-run + # Пароль берётся из переменной окружения SSHPASS (см. README.md / DEPLOY_INSTRUCTIONS.md) endif -.PHONY: build deploy deploy-dry-run clean rebuild help +.PHONY: build deploy deploy-dry-run clean rebuild help venv build: hugo --minify @echo "Сайт успешно собран в папку public/" -deploy: build +# ЕДИНСТВЕННЫЙ способ деплоя — через make deploy. +# Никогда не заменять на rsync/ssh-exec напрямую: на Jino аккаунт SFTP-only. +deploy: build venv ifeq ($(PLATFORM),Windows) $(DEPLOY_CMD) else - rsync -avz --delete -e 'ssh -p $(PORT)' public/ $(USERNAME)@$(HOST):$(REMOTE_PATH)/ + @test -n "$$SSHPASS" || (echo "ОШИБКА: переменная SSHPASS (пароль SFTP) не задана. См. README.md / DEPLOY_INSTRUCTIONS.md"; exit 1) + $(DEPLOY_CMD) endif -deploy-dry-run: build +deploy-dry-run: build venv ifeq ($(PLATFORM),Windows) @echo "Выполняем проверку соединения с сервером..." $(DRYRUN_CMD) else - rsync -avn --delete -e 'ssh -p $(PORT)' public/ $(USERNAME)@$(HOST):$(REMOTE_PATH)/ + @test -n "$$SSHPASS" || (echo "ОШИБКА: переменная SSHPASS (пароль SFTP) не задана. См. README.md / DEPLOY_INSTRUCTIONS.md"; exit 1) + $(DRYRUN_CMD) endif + +# Создаёт venv с paramiko для Unix-деплоя (единожды) +venv: + @if [ ! -x .venv/bin/python ]; then \ + echo "Создаю .venv и ставлю paramiko..."; \ + python3 -m venv .venv && .venv/bin/pip install -q paramiko; \ + fi clean: ifeq ($(OS),Windows_NT) - del /q /s public\ + del /q /s public\/ else rm -rf public/ endif @@ -46,8 +59,11 @@ rebuild: clean deploy help: @echo "Доступные команды:" @echo " make build - собрать сайт" - @echo " make deploy - собрать и задеплоить сайт" - @echo " make deploy-dry-run - симуляция деплоя" + @echo " make deploy - собрать и задеплоить сайт (SFTP через lftp, требуется SSHPASS)" + @echo " make deploy-dry-run - симуляция деплоя (требуется SSHPASS)" @echo " make clean - удалить папку public" @echo " make rebuild - полная пересборка сайта" - @echo " make help - показать это сообщение" \ No newline at end of file + @echo " make help - показать это сообщение" + @echo "" + @echo "Деплой: export SSHPASS='<пароль SFTP>'; make deploy" + @echo "Пароль хранится в DEPLOY_INSTRUCTIONS.md (winscp_script.txt для Windows)." \ No newline at end of file diff --git a/README.md b/README.md index 5417ad1..f6f8fa3 100644 --- a/README.md +++ b/README.md @@ -71,22 +71,30 @@ ## Деплой сайта -Для автоматизации процесса деплоя созданы специальные файлы: +> **ЕДИНСТВЕННЫЙ способ деплоя сайта — `make deploy`.** Не деплойте вручную +> (rsync/ssh-exec напрямую не работают: аккаунт Jino SFTP-only). -- `Makefile` - содержит команды для сборки и публикации сайта на хостинге (работает как на Unix-подобных системах, так и на Windows) -- `winscp_script.txt` - скрипт для WinSCP с командами подключения и синхронизации -- `DEPLOY_INSTRUCTIONS.md` - инструкция по использованию команд деплоя +Специальные файлы: -Для публикации сайта на хостинге используйте команду: +- `Makefile` — команды сборки и публикации (Unix: SFTP через `tools/deploy_sftp.py` + на paramiko; Windows: WinSCP.com) +- `tools/deploy_sftp.py` — рекурсивный mirror `public/` → `/dedinit.ru` + (с удалением лишнего, как `rsync --delete`), пароль из env `SSHPASS` +- `winscp_script.txt` — скрипт для WinSCP (Windows) +- `DEPLOY_INSTRUCTIONS.md` — инструкция по деплою + +Для публикации сайта на хостинге: ```shell +export SSHPASS='<пароль SFTP из DEPLOY_INSTRUCTIONS.md>' make deploy ``` -Для получения полного списка доступных команд используйте: +Первая сборка создаст `.venv/` (paramiko). Для получения полного списка команд: ```shell make help ``` -Процесс деплоя кроссплатформенный: на Unix-подобных системах используется rsync, на Windows - WinSCP.com для SFTP-подключения +Процесс кроссплатформенный: на Unix — SFTP (paramiko), на Windows — WinSCP.com. +НЕ удаляйте `make deploy` и не заменяйте его на прямой rsync. diff --git a/tools/deploy_sftp.py b/tools/deploy_sftp.py new file mode 100644 index 0000000..b7679ff --- /dev/null +++ b/tools/deploy_sftp.py @@ -0,0 +1,102 @@ +#!/usr/bin/env python3 +"""Deploy public/ -> /dedinit.ru on Jino (SFTP-only, password auth). + +Используется командой `make deploy` (единственный способ деплоя сайта). +Jino-аккаунт kpa39l_dedinit — SFTP-only: удалённый exec (rsync/ssh) запрещён, +поэтому заливаем через SFTP с рекурсивным mirror и --delete (как rsync). +Пароль — из env SSHPASS (см. README.md / DEPLOY_INSTRUCTIONS.md). +""" +import os +import stat as stmod +import sys + +try: + import paramiko +except ImportError: + print("ОШИБКА: нет paramiko. Установите: pip install paramiko", file=sys.stderr) + sys.exit(1) + +HOST = os.environ.get("DEDINIT_HOST", "kpa39l.myjino.ru") +PORT = int(os.environ.get("DEDINIT_PORT", "2222")) +USER = os.environ.get("DEDINIT_USER", "kpa39l_dedinit") +PASS = os.environ.get("SSHPASS", "") +LOCAL = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "public") +REMOTE = os.environ.get("DEDINIT_REMOTE", "/dedinit.ru") +DRYRUN = "--dry-run" in sys.argv + +if not PASS: + print("ОШИБКА: переменная SSHPASS (пароль SFTP) не задана. См. README.md / DEPLOY_INSTRUCTIONS.md", file=sys.stderr) + sys.exit(1) + +LOCAL = os.path.abspath(LOCAL) +if not os.path.isdir(LOCAL): + print(f"ОШИБКА: нет каталога {LOCAL}. Сначала: make build", file=sys.stderr) + sys.exit(1) + +t = paramiko.Transport((HOST, PORT)) +t.connect(username=USER, password=PASS) +sftp = paramiko.SFTPClient.from_transport(t) + + +def rlist(path): + out = [] + try: + entries = sftp.listdir_attr(path) + except IOError: + return out + for e in entries: + full = os.path.join(path, e.filename) + rel = os.path.relpath(full, REMOTE) + isdir = stmod.S_ISDIR(e.st_mode) + out.append((rel, isdir)) + if isdir: + out.extend(rlist(full)) + return out + + +print(f"Копирую {LOCAL} -> sftp://{USER}@{HOST}:{PORT}{REMOTE}/" + + (" (DRY-RUN)" if DRYRUN else "")) + +local_files = [] +for root, dirs, files in os.walk(LOCAL): + for f in files: + local_files.append(os.path.relpath(os.path.join(root, f), LOCAL)) + +if not DRYRUN: + remote_entries = rlist(REMOTE) + remote_files = {r for r, d in remote_entries if not d} + +for rel in sorted(local_files): + if DRYRUN: + print(" +", rel) + continue + lpath = os.path.join(LOCAL, rel) + rpath = os.path.join(REMOTE, rel) + rdir = os.path.dirname(rpath) + cur = REMOTE + for seg in os.path.relpath(rdir, REMOTE).split(os.sep): + if not seg: + continue + cur = os.path.join(cur, seg) + try: + sftp.stat(cur) + except IOError: + try: + sftp.mkdir(cur) + except IOError: + pass + sftp.put(lpath, rpath) + print(" +", rel) + +if not DRYRUN: + to_delete = remote_files - set(local_files) + for rel in sorted(to_delete): + try: + sftp.remove(os.path.join(REMOTE, rel)) + print(" -", rel) + except Exception as e: + print(" !", rel, e) + +sftp.close() +t.close() +print("Готово." if not DRYRUN else "DRY-RUN завершён: показаны изменения без заливки.") \ No newline at end of file