#!/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 завершён: показаны изменения без заливки.")