-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdeploy.py
More file actions
137 lines (109 loc) · 4.14 KB
/
Copy pathdeploy.py
File metadata and controls
137 lines (109 loc) · 4.14 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
"""
Deploy mesh2param to the existing Hetzner VPS (alongside operatorDashboard).
Uses the same SSH + Docker + Caddy pattern from operatorDashboard.
mesh2param runs on port 8081 (operator uses 8000/3000).
Usage:
python deploy.py # deploy to VPS_IP from .env
python deploy.py --ip 1.2.3.4 # deploy to specific IP
python deploy.py --domain mesh2param.com # configure HTTPS via Caddy
Requires:
- SSH key at ~/.ssh/id_ed25519 (same as operatorDashboard)
- VPS already provisioned with Docker + Caddy (operatorDashboard's cloud-init)
"""
import argparse
import asyncio
import os
import subprocess
import sys
from dotenv import load_dotenv
load_dotenv()
_GITHUB_TOKEN = os.getenv("GITHUB_TOKEN", "")
_REPO_BASE = "github.com/TrentIndeed/meshToParametric.git"
DEPLOY_REPO = f"https://{_GITHUB_TOKEN}@{_REPO_BASE}" if _GITHUB_TOKEN else f"https://{_REPO_BASE}"
DEPLOY_BRANCH = os.getenv("DEPLOY_BRANCH", "main")
SSH_KEY_PATH = os.getenv("SSH_KEY_PATH", os.path.expanduser("~/.ssh/id_ed25519"))
VPS_IP = os.getenv("VPS_IP", "")
async def deploy(ip: str, domain: str = "", auth_secret: str = ""):
"""Deploy mesh2param to the VPS."""
if not auth_secret:
auth_secret = os.getenv("AUTH_SECRET", "mesh2param-default-secret-change-me")
# Caddy config block — appends to existing Caddyfile
caddy_block = ""
if domain:
caddy_block = f"""
# Add mesh2param to Caddy config only if not already present
if ! grep -q '{domain}' /etc/caddy/Caddyfile 2>/dev/null; then
cat >> /etc/caddy/Caddyfile << 'CADDY'
{domain} {{
reverse_proxy localhost:8081
}}
CADDY
fi
systemctl reload caddy
"""
deploy_script = f"""#!/bin/bash
set -e
echo "=== Deploying mesh2param ==="
# Clone or pull
if [ -d /opt/meshToParametric ]; then
cd /opt/meshToParametric && git pull origin {DEPLOY_BRANCH}
else
cd /opt && git clone -b {DEPLOY_BRANCH} {DEPLOY_REPO}
fi
cd /opt/meshToParametric
# Write .env
cat > .env << 'ENVEOF'
AUTH_SECRET={auth_secret}
DATABASE_URL=sqlite:////app/data/mesh2param.db
ENVEOF
# Build and deploy (port 8081, doesn't conflict with operator on 8000/3000)
# --build rebuilds the image, container restarts and clears in-memory jobs
docker compose up -d --build
echo "NOTE: Container restart clears in-memory job data. User accounts persist in SQLite."
{caddy_block}
echo "DEPLOY_COMPLETE"
"""
print(f"Deploying to {ip}...")
result = await _ssh_exec(ip, deploy_script)
if "DEPLOY_COMPLETE" in result:
url = f"https://{domain}" if domain else f"http://{ip}:8081"
print(f"\nDeployed successfully!")
print(f" App: {url}")
print(f" Landing: {url}/landing")
print(f" API: {url}/api/status")
else:
print(f"\nDeploy may have failed:\n{result[-500:]}")
async def _ssh_exec(ip: str, script: str) -> str:
"""Execute a script on the remote server via SSH."""
proc = await asyncio.create_subprocess_exec(
"ssh",
"-o", "StrictHostKeyChecking=no",
"-o", "ConnectTimeout=30",
"-i", SSH_KEY_PATH,
f"root@{ip}",
"bash", "-s",
stdin=asyncio.subprocess.PIPE,
stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE,
)
stdout, stderr = await asyncio.wait_for(
proc.communicate(script.encode()),
timeout=600, # 10 min for Docker build
)
output = stdout.decode() + stderr.decode()
print(output[-2000:]) # Show tail of output
if proc.returncode != 0:
raise RuntimeError(f"SSH failed (exit {proc.returncode})")
return output
def main():
parser = argparse.ArgumentParser(description="Deploy mesh2param to Hetzner VPS")
parser.add_argument("--ip", default=VPS_IP, help="VPS IP address (or set VPS_IP in .env)")
parser.add_argument("--domain", default="parameshai.com", help="Domain for HTTPS")
parser.add_argument("--auth-secret", default="", help="AUTH_SECRET for production")
args = parser.parse_args()
if not args.ip:
print("Error: No VPS IP. Use --ip or set VPS_IP in .env")
sys.exit(1)
asyncio.run(deploy(args.ip, args.domain, args.auth_secret))
if __name__ == "__main__":
main()