This repository was archived by the owner on Jul 6, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 84
Expand file tree
/
Copy pathdeploy_backend.py
More file actions
164 lines (146 loc) · 5.27 KB
/
Copy pathdeploy_backend.py
File metadata and controls
164 lines (146 loc) · 5.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
# -*- coding: utf-8 -*-
"""
Deploy backend to BaoTa server + upload frontend dist.
Usage: python deploy_backend.py
"""
import os, sys, tarfile, io, time
import paramiko
HOST = "38.175.195.71"
USER = "root"
PASS = "lFjTQo8NXHN7TfCI"
REMOTE_DIR = "/opt/noterx"
FRONTEND_DIR = "/www/wwwroot/noterx.muran.tech"
# -- helper --
def run(ssh, cmd, check=True):
print(f" $ {cmd[:120]}")
stdin, stdout, stderr = ssh.exec_command(cmd, timeout=300)
out = stdout.read().decode("utf-8", errors="replace")
err = stderr.read().decode("utf-8", errors="replace")
code = stdout.channel.recv_exit_status()
if check and code != 0:
print(f" FAIL (exit {code}): {err[:300]}")
sys.exit(1)
if out.strip():
for line in out.strip().split("\n")[:5]:
print(f" {line}")
return out, err, code
# ============ 1. Connect ============
print(f"[1/6] Connecting to {HOST}...")
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(HOST, username=USER, password=PASS, timeout=15)
sftp = ssh.open_sftp()
print(" OK")
# ============ 2. Pack ============
print("[2/6] Packing project...")
buf = io.BytesIO()
root = os.path.dirname(os.path.abspath(__file__))
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
for folder in ["backend", "scripts", "docs"]:
p = os.path.join(root, folder)
if os.path.isdir(p):
# skip venv, __pycache__, .env (we upload .env separately)
tar.add(p, arcname=folder,
filter=lambda info: None if "__pycache__" in info.name or "venv" in info.name or info.name.endswith(".pyc") else info)
# frontend dist
dist = os.path.join(root, "frontend", "dist")
if os.path.isdir(dist):
tar.add(dist, arcname="frontend_dist")
buf.seek(0)
print(f" Packed {len(buf.getvalue())/1024:.0f} KB")
# ============ 3. Upload ============
remote_tar = "/tmp/noterx_deploy.tar.gz"
print("[3/6] Uploading...")
sftp.putfo(buf, remote_tar)
print(" OK")
# ============ 4. Extract + Frontend ============
print("[4/6] Extracting...")
run(ssh, f"mkdir -p {REMOTE_DIR}")
run(ssh, f"cd {REMOTE_DIR} && tar xzf {remote_tar}")
run(ssh, f"rm -f {remote_tar}")
# Copy frontend dist to BaoTa site
print(" Copying frontend dist to BaoTa site...")
run(ssh, f"mkdir -p {FRONTEND_DIR}")
run(ssh, f"rm -rf {FRONTEND_DIR}/*")
run(ssh, f"cp -r {REMOTE_DIR}/frontend_dist/* {FRONTEND_DIR}/")
run(ssh, f"rm -rf {REMOTE_DIR}/frontend_dist")
print(" Frontend OK")
# ============ 5. Backend deps ============
print("[5/6] Installing backend dependencies (may take a while)...")
py3 = "python3"
out, _, _ = run(ssh, f"{py3} --version", check=False)
has_py3 = "Python 3" in out
PY = py3 if has_py3 else "python"
# Install python3-venv if missing (Debian/Ubuntu)
run(ssh, "apt-get install -y python3-venv python3-pip", check=False)
run(ssh, f"rm -rf {REMOTE_DIR}/backend/venv")
run(ssh, f"{PY} -m venv {REMOTE_DIR}/backend/venv")
run(ssh, f"{REMOTE_DIR}/backend/venv/bin/pip install --upgrade pip -q")
run(ssh, f"{REMOTE_DIR}/backend/venv/bin/pip install -r {REMOTE_DIR}/backend/requirements.txt")
# Init DB
print(" Initializing database...")
run(ssh, f"cd {REMOTE_DIR} && {REMOTE_DIR}/backend/venv/bin/python scripts/init_db.py", check=False)
run(ssh, f"cd {REMOTE_DIR} && {REMOTE_DIR}/backend/venv/bin/python scripts/seed_data.py", check=False)
run(ssh, f"cd {REMOTE_DIR} && {REMOTE_DIR}/backend/venv/bin/python scripts/compute_baseline.py", check=False)
# Upload .env
print(" Uploading .env...")
local_env = os.path.join(root, "backend", ".env")
if os.path.isfile(local_env):
sftp.put(local_env, f"{REMOTE_DIR}/backend/.env")
print(" .env uploaded")
# ============ 6. Systemd service ============
print("[6/6] Setting up systemd service...")
SERVICE = """[Unit]
Description=NoteRx Backend
After=network.target
[Service]
Type=simple
WorkingDirectory=/opt/noterx/backend
ExecStart=/opt/noterx/backend/venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8000
Restart=always
RestartSec=5
Environment=PYTHONUNBUFFERED=1
[Install]
WantedBy=multi-user.target
"""
with sftp.open("/etc/systemd/system/noterx.service", "w") as f:
f.write(SERVICE)
run(ssh, "systemctl daemon-reload")
run(ssh, "systemctl enable noterx")
run(ssh, "systemctl restart noterx")
time.sleep(3)
run(ssh, "systemctl status noterx --no-pager -l", check=False)
# Health check
out, _, _ = run(ssh, "curl -s http://127.0.0.1:8000/api/health", check=False)
print("")
print("=" * 50)
print("DEPLOY DONE!")
print(f" Backend API: http://127.0.0.1:8000 (Nginx proxy)")
print(f" Frontend: {FRONTEND_DIR}")
print(f" Admin: https://noterx.muran.tech/admin")
print(f" Password: pageone")
print("")
print("Nginx config needed (see below):")
print("""
location /api/ {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /admin {
proxy_pass http://127.0.0.1:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
location /terms {
proxy_pass http://127.0.0.1:8000;
}
location /privacy {
proxy_pass http://127.0.0.1:8000;
}
""")
print("=" * 50)
sftp.close()
ssh.close()