chore(deploy): make deploy — единственный способ деплоя (SFTP paramiko вместо rsync)
- Makefile Unix: tools/deploy_sftp.py (paramiko mirror --delete), autovenv - tools/deploy_sftp.py: рекурсивный SFTP-mirror public/ -> /dedinit.ru - Jino SFTP-only: rsync/ssh-exec запрещены (exec request failed on channel 0) - README/DEPLOY_INSTRUCTIONS: обновлены, деплой только через make deploy - .gitignore: + .venv/
This commit is contained in:
@@ -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 завершён: показаны изменения без заливки.")
|
||||
Reference in New Issue
Block a user