Add tools
This commit is contained in:
135
deploy_vps.py
Normal file
135
deploy_vps.py
Normal file
@@ -0,0 +1,135 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import posixpath
|
||||
import stat
|
||||
import tarfile
|
||||
import time
|
||||
import socket
|
||||
from pathlib import Path
|
||||
|
||||
import paramiko
|
||||
|
||||
HOST = "2.26.123.84"
|
||||
USER = "root"
|
||||
PASSWORD = os.environ["VPS_PASSWORD"]
|
||||
LOCAL_ROOT = Path(__file__).resolve().parent
|
||||
ARCHIVE = LOCAL_ROOT / "iaai_deploy.tar.gz"
|
||||
REMOTE_DIR = "/root/iaai-parser"
|
||||
REMOTE_ARCHIVE = "/root/iaai_deploy.tar.gz"
|
||||
|
||||
EXCLUDE_DIRS = {
|
||||
".git",
|
||||
".venv",
|
||||
"__pycache__",
|
||||
".pytest_cache",
|
||||
".mypy_cache",
|
||||
".ruff_cache",
|
||||
"iaai_scraper.egg-info",
|
||||
}
|
||||
EXCLUDE_FILES = {"iaai_deploy.tar.gz", "deploy_vps.py"}
|
||||
|
||||
|
||||
def _include(path: Path) -> bool:
|
||||
rel = path.relative_to(LOCAL_ROOT)
|
||||
parts = set(rel.parts)
|
||||
if parts & EXCLUDE_DIRS:
|
||||
return False
|
||||
if path.name in EXCLUDE_FILES:
|
||||
return False
|
||||
if path.suffix in {".pyc", ".pyo"}:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def make_archive() -> None:
|
||||
if ARCHIVE.exists():
|
||||
ARCHIVE.unlink()
|
||||
with tarfile.open(ARCHIVE, "w:gz") as tf:
|
||||
for path in LOCAL_ROOT.rglob("*"):
|
||||
if not _include(path):
|
||||
continue
|
||||
tf.add(path, arcname=str(path.relative_to(LOCAL_ROOT)))
|
||||
print(f"ARCHIVE_READY {ARCHIVE} {ARCHIVE.stat().st_size} bytes", flush=True)
|
||||
|
||||
|
||||
def connect() -> paramiko.SSHClient:
|
||||
last_exc: BaseException | None = None
|
||||
for attempt in range(1, 6):
|
||||
client = paramiko.SSHClient()
|
||||
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
try:
|
||||
print(f"SSH_CONNECT attempt={attempt}", flush=True)
|
||||
client.connect(
|
||||
HOST,
|
||||
username=USER,
|
||||
password=PASSWORD,
|
||||
look_for_keys=False,
|
||||
allow_agent=False,
|
||||
timeout=45,
|
||||
banner_timeout=60,
|
||||
auth_timeout=45,
|
||||
)
|
||||
return client
|
||||
except (paramiko.SSHException, socket.timeout, OSError) as exc:
|
||||
last_exc = exc
|
||||
client.close()
|
||||
print(f"SSH_CONNECT_RETRY attempt={attempt} error={type(exc).__name__}: {exc}", flush=True)
|
||||
time.sleep(3 * attempt)
|
||||
raise SystemExit(f"SSH_CONNECT_FAILED: {last_exc}")
|
||||
|
||||
|
||||
def run(client: paramiko.SSHClient, cmd: str, timeout: int | None = None) -> None:
|
||||
print(f"REMOTE_RUN {cmd}", flush=True)
|
||||
stdin, stdout, stderr = client.exec_command(cmd, timeout=timeout)
|
||||
exit_code = stdout.channel.recv_exit_status()
|
||||
out = stdout.read().decode("utf-8", "replace")
|
||||
err = stderr.read().decode("utf-8", "replace")
|
||||
if out:
|
||||
print(out, end="", flush=True)
|
||||
if err:
|
||||
print(err, end="", flush=True)
|
||||
if exit_code != 0:
|
||||
raise SystemExit(f"REMOTE_FAILED code={exit_code}: {cmd}")
|
||||
|
||||
|
||||
def upload_file(sftp: paramiko.SFTPClient, local: Path, remote: str) -> None:
|
||||
total = local.stat().st_size
|
||||
last = 0.0
|
||||
|
||||
def cb(done: int, _total: int) -> None:
|
||||
nonlocal last
|
||||
now = time.time()
|
||||
if now - last >= 2 or done == total:
|
||||
print(f"UPLOAD {done}/{total}", flush=True)
|
||||
last = now
|
||||
|
||||
sftp.put(str(local), remote, callback=cb)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
make_archive()
|
||||
client = connect()
|
||||
try:
|
||||
run(client, "echo VPS_OK && hostname && docker --version && docker compose version")
|
||||
sftp = client.open_sftp()
|
||||
try:
|
||||
upload_file(sftp, ARCHIVE, REMOTE_ARCHIVE)
|
||||
finally:
|
||||
sftp.close()
|
||||
run(
|
||||
client,
|
||||
f"mkdir -p {REMOTE_DIR} && cd {REMOTE_DIR} && docker compose down || true && "
|
||||
f"find {REMOTE_DIR} -mindepth 1 -maxdepth 1 ! -name '.env' -exec rm -rf {{}} + && "
|
||||
f"tar -xzf {REMOTE_ARCHIVE} -C {REMOTE_DIR} && rm -f {REMOTE_ARCHIVE}",
|
||||
timeout=300,
|
||||
)
|
||||
run(client, f"cd {REMOTE_DIR} && docker compose up -d --build", timeout=1800)
|
||||
run(client, f"cd {REMOTE_DIR} && docker compose ps", timeout=120)
|
||||
run(client, "docker logs --since 2m iaai-parser-worker-1 2>&1 | tail -n 120 || docker compose -f /root/iaai-parser/docker-compose.yml logs --since 2m worker | tail -n 120", timeout=120)
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user