Add tools
This commit is contained in:
37
check_vps.py
Normal file
37
check_vps.py
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import paramiko
|
||||||
|
|
||||||
|
HOST = "2.26.123.84"
|
||||||
|
USER = "root"
|
||||||
|
PASSWORD = os.environ["VPS_PASSWORD"]
|
||||||
|
|
||||||
|
cmds = [
|
||||||
|
"cd /root/iaai-parser && docker compose ps",
|
||||||
|
"docker logs --since 3m iaai-parser-worker-1 2>&1 | tail -n 120",
|
||||||
|
"docker exec -i iaai-postgres psql -U iaai -d iaai_scraper -c \"SELECT now() AS ts, count(*) AS cars FROM iaai_cars; SELECT count(*) AS runs FROM iaai_sync_runs;\"",
|
||||||
|
]
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect(
|
||||||
|
HOST,
|
||||||
|
username=USER,
|
||||||
|
password=PASSWORD,
|
||||||
|
look_for_keys=False,
|
||||||
|
allow_agent=False,
|
||||||
|
timeout=30,
|
||||||
|
banner_timeout=30,
|
||||||
|
auth_timeout=30,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
for cmd in cmds:
|
||||||
|
print(f"REMOTE_RUN {cmd}", flush=True)
|
||||||
|
stdin, stdout, stderr = client.exec_command(cmd, timeout=120)
|
||||||
|
code = stdout.channel.recv_exit_status()
|
||||||
|
print(stdout.read().decode("utf-8", "replace"), end="")
|
||||||
|
print(stderr.read().decode("utf-8", "replace"), end="")
|
||||||
|
print(f"EXIT {code}", flush=True)
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
41
clean_vps_db.py
Normal file
41
clean_vps_db.py
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import paramiko
|
||||||
|
|
||||||
|
HOST = "2.26.123.84"
|
||||||
|
USER = "root"
|
||||||
|
PASSWORD = os.environ["VPS_PASSWORD"]
|
||||||
|
|
||||||
|
commands = [
|
||||||
|
"cd /root/iaai-parser && docker exec -i iaai-postgres psql -U iaai -d iaai_scraper -c 'TRUNCATE TABLE iaai_images, iaai_cars, iaai_sync_runs RESTART IDENTITY CASCADE;'",
|
||||||
|
"docker exec -i iaai-redis redis-cli FLUSHALL",
|
||||||
|
"docker exec -i iaai-postgres psql -U iaai -d iaai_scraper -c 'SELECT count(*) AS cars FROM iaai_cars; SELECT count(*) AS runs FROM iaai_sync_runs;'",
|
||||||
|
"cd /root/iaai-parser && docker compose ps",
|
||||||
|
"docker logs --since 2m iaai-parser-worker-1 2>&1 | tail -n 100",
|
||||||
|
]
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect(
|
||||||
|
HOST,
|
||||||
|
username=USER,
|
||||||
|
password=PASSWORD,
|
||||||
|
look_for_keys=False,
|
||||||
|
allow_agent=False,
|
||||||
|
timeout=30,
|
||||||
|
banner_timeout=30,
|
||||||
|
auth_timeout=30,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
for command in commands:
|
||||||
|
print(f"REMOTE_RUN {command}", flush=True)
|
||||||
|
stdin, stdout, stderr = client.exec_command(command, timeout=180)
|
||||||
|
exit_code = stdout.channel.recv_exit_status()
|
||||||
|
print(stdout.read().decode("utf-8", "replace"), end="")
|
||||||
|
print(stderr.read().decode("utf-8", "replace"), end="")
|
||||||
|
print(f"EXIT {exit_code}", flush=True)
|
||||||
|
if exit_code != 0:
|
||||||
|
raise SystemExit(exit_code)
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
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()
|
||||||
38
status_vps.py
Normal file
38
status_vps.py
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import os
|
||||||
|
import paramiko
|
||||||
|
|
||||||
|
HOST = "2.26.123.84"
|
||||||
|
USER = "root"
|
||||||
|
PASSWORD = os.environ["VPS_PASSWORD"]
|
||||||
|
|
||||||
|
commands = [
|
||||||
|
"cd /root/iaai-parser && docker compose ps",
|
||||||
|
"cd /root/iaai-parser && docker compose exec -T worker celery -A iaai_scraper.worker.celery_app inspect active reserved scheduled",
|
||||||
|
"docker exec -i iaai-postgres psql -U iaai -d iaai_scraper -c \"SELECT now() AS ts, (SELECT count(*) FROM iaai_cars) AS cars, (SELECT count(*) FROM iaai_sync_runs) AS runs, (SELECT status FROM iaai_sync_runs ORDER BY id DESC LIMIT 1) AS last_status, (SELECT cars_upserted FROM iaai_sync_runs ORDER BY id DESC LIMIT 1) AS last_upserted, (SELECT cars_failed FROM iaai_sync_runs ORDER BY id DESC LIMIT 1) AS last_failed, (SELECT started_at FROM iaai_sync_runs ORDER BY id DESC LIMIT 1) AS last_started;\"",
|
||||||
|
"docker logs --since 10m iaai-parser-worker-1 2>&1 | tail -n 160",
|
||||||
|
]
|
||||||
|
|
||||||
|
client = paramiko.SSHClient()
|
||||||
|
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||||
|
client.connect(
|
||||||
|
HOST,
|
||||||
|
username=USER,
|
||||||
|
password=PASSWORD,
|
||||||
|
look_for_keys=False,
|
||||||
|
allow_agent=False,
|
||||||
|
timeout=30,
|
||||||
|
banner_timeout=30,
|
||||||
|
auth_timeout=30,
|
||||||
|
)
|
||||||
|
try:
|
||||||
|
for command in commands:
|
||||||
|
print(f"\n=== REMOTE_RUN {command} ===", flush=True)
|
||||||
|
stdin, stdout, stderr = client.exec_command(command, timeout=180)
|
||||||
|
exit_code = stdout.channel.recv_exit_status()
|
||||||
|
print(stdout.read().decode("utf-8", "replace"), end="")
|
||||||
|
print(stderr.read().decode("utf-8", "replace"), end="")
|
||||||
|
print(f"\n=== EXIT {exit_code} ===", flush=True)
|
||||||
|
finally:
|
||||||
|
client.close()
|
||||||
Reference in New Issue
Block a user