ttsd-colabcli 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli.js +148 -0
- package/core/app/__init__.py +0 -0
- package/core/app/colab_cli/__init__.py +0 -0
- package/core/app/colab_cli/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/auth.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/auto_update.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/cli.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/client.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/common.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/console.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/contents.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/history.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/runtime.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/state.cpython-312.pyc +0 -0
- package/core/app/colab_cli/__pycache__/utils.cpython-312.pyc +0 -0
- package/core/app/colab_cli/auth.py +278 -0
- package/core/app/colab_cli/auto_update.py +248 -0
- package/core/app/colab_cli/cli.py +155 -0
- package/core/app/colab_cli/client.py +310 -0
- package/core/app/colab_cli/commands/__init__.py +14 -0
- package/core/app/colab_cli/commands/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/automation.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/execution.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/files.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/run.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/session.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/__pycache__/utility.cpython-312.pyc +0 -0
- package/core/app/colab_cli/commands/automation.py +265 -0
- package/core/app/colab_cli/commands/execution.py +362 -0
- package/core/app/colab_cli/commands/files.py +204 -0
- package/core/app/colab_cli/commands/run.py +477 -0
- package/core/app/colab_cli/commands/session.py +519 -0
- package/core/app/colab_cli/commands/utility.py +436 -0
- package/core/app/colab_cli/common.py +185 -0
- package/core/app/colab_cli/console.py +172 -0
- package/core/app/colab_cli/contents.py +93 -0
- package/core/app/colab_cli/converter.py +184 -0
- package/core/app/colab_cli/history.py +65 -0
- package/core/app/colab_cli/oauth_config.json +11 -0
- package/core/app/colab_cli/repl.py +173 -0
- package/core/app/colab_cli/runtime.py +262 -0
- package/core/app/colab_cli/state.py +156 -0
- package/core/app/colab_cli/utils.py +85 -0
- package/core/colab/worker.py +679 -0
- package/core/daemon.py +184 -0
- package/core/requirements.txt +8 -0
- package/package.json +22 -0
package/core/daemon.py
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import uuid
|
|
5
|
+
import time
|
|
6
|
+
import httpx
|
|
7
|
+
import argparse
|
|
8
|
+
import base64
|
|
9
|
+
import logging
|
|
10
|
+
import signal
|
|
11
|
+
from dotenv import load_dotenv
|
|
12
|
+
|
|
13
|
+
load_dotenv()
|
|
14
|
+
|
|
15
|
+
# Add project root to python path so we can import app.colab_cli
|
|
16
|
+
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), "..")))
|
|
17
|
+
|
|
18
|
+
from app.colab_cli.auth import AuthProvider, get_credentials
|
|
19
|
+
from app.colab_cli.client import Client, Prod, Variant, Accelerator, ColabRequestError, TooManyAssignmentsError
|
|
20
|
+
from app.colab_cli.common import State
|
|
21
|
+
from app.colab_cli.commands.session import spawn_keep_alive
|
|
22
|
+
from app.colab_cli.state import SessionState
|
|
23
|
+
|
|
24
|
+
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(name)s: %(message)s")
|
|
25
|
+
logger = logging.getLogger("satellite")
|
|
26
|
+
|
|
27
|
+
MASTER_URL = os.getenv("MASTER_URL", "http://localhost:8090")
|
|
28
|
+
NODE_API_KEY = os.getenv("NODE_API_KEY", "satellite-secret-key-123")
|
|
29
|
+
NODE_ID = os.getenv("NODE_ID", f"node-{uuid.uuid4().hex[:6]}")
|
|
30
|
+
|
|
31
|
+
HEADERS = {"x-node-key": NODE_API_KEY}
|
|
32
|
+
|
|
33
|
+
async def fetch_token(email: str):
|
|
34
|
+
token_dir = os.path.expanduser("~/.config/colab-cli")
|
|
35
|
+
os.makedirs(token_dir, exist_ok=True)
|
|
36
|
+
safe = email.replace("@", "_at_").replace(".", "_")
|
|
37
|
+
token_path = os.path.join(token_dir, f"token_{safe}.json")
|
|
38
|
+
|
|
39
|
+
async with httpx.AsyncClient() as client:
|
|
40
|
+
res = await client.get(f"{MASTER_URL}/api/node/token/{email}", headers=HEADERS)
|
|
41
|
+
if res.status_code == 200:
|
|
42
|
+
with open(token_path, "w") as f:
|
|
43
|
+
f.write(res.text)
|
|
44
|
+
return token_path
|
|
45
|
+
raise Exception(f"Failed to fetch token: {res.text}")
|
|
46
|
+
|
|
47
|
+
async def report_status(email: str, status: str, colab_pid: int = None, worker_session_id: str = None, error: str = None):
|
|
48
|
+
async with httpx.AsyncClient() as client:
|
|
49
|
+
payload = {
|
|
50
|
+
"email": email,
|
|
51
|
+
"status": status,
|
|
52
|
+
"colab_pid": colab_pid,
|
|
53
|
+
"worker_session_id": worker_session_id,
|
|
54
|
+
"error": error
|
|
55
|
+
}
|
|
56
|
+
await client.post(f"{MASTER_URL}/api/node/status", json=payload, headers=HEADERS)
|
|
57
|
+
|
|
58
|
+
async def launch_worker(email: str, public_url: str):
|
|
59
|
+
logger.info("Launching worker for %s", email)
|
|
60
|
+
|
|
61
|
+
try:
|
|
62
|
+
token_path = await fetch_token(email)
|
|
63
|
+
except Exception as e:
|
|
64
|
+
logger.error("Failed to fetch token: %s", e)
|
|
65
|
+
await report_status(email, "FAILED", error=str(e))
|
|
66
|
+
return
|
|
67
|
+
|
|
68
|
+
_creds = get_credentials(provider=AuthProvider.OAUTH2, token_path=token_path)
|
|
69
|
+
_client = Client(Prod(), _creds)
|
|
70
|
+
|
|
71
|
+
_state = State()
|
|
72
|
+
_state.config_path = os.path.expanduser(f"~/.config/colab-cli/store_{email.replace('@', '_at_').replace('.', '_')}.json")
|
|
73
|
+
_state.auth_provider = AuthProvider.OAUTH2
|
|
74
|
+
_state._client = _client
|
|
75
|
+
|
|
76
|
+
name = f"worker-{NODE_ID}-{uuid.uuid4().hex[:4]}"
|
|
77
|
+
variant = Variant.GPU
|
|
78
|
+
accelerator = Accelerator.T4
|
|
79
|
+
|
|
80
|
+
loop = asyncio.get_running_loop()
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
res = await loop.run_in_executor(None, _client.assign, uuid.uuid4(), variant, accelerator)
|
|
84
|
+
except Exception as e:
|
|
85
|
+
logger.error("Assign failed for %s: %s", email, e)
|
|
86
|
+
await report_status(email, "FAILED", error=str(e))
|
|
87
|
+
return
|
|
88
|
+
|
|
89
|
+
token = res.runtime_proxy_info.token if hasattr(res, "runtime_proxy_info") else getattr(res, "runtime_proxy_token", "")
|
|
90
|
+
url = res.runtime_proxy_info.url if hasattr(res, "runtime_proxy_info") else ""
|
|
91
|
+
endpoint = res.endpoint
|
|
92
|
+
|
|
93
|
+
s = SessionState(name=name, token=token, url=url, endpoint=endpoint, variant=variant.value, accelerator=accelerator.value)
|
|
94
|
+
|
|
95
|
+
try:
|
|
96
|
+
await loop.run_in_executor(None, _client.keep_alive_assignment, endpoint)
|
|
97
|
+
except Exception as e:
|
|
98
|
+
logger.warning("Keep-alive failed for %s: %s", email, e)
|
|
99
|
+
|
|
100
|
+
_state.store.add(s)
|
|
101
|
+
pid = spawn_keep_alive(endpoint, name, auth_provider=_state.auth_provider, config_path=_state.config_path, token_path=token_path)
|
|
102
|
+
s.keep_alive_pid = pid
|
|
103
|
+
_state.store.add(s)
|
|
104
|
+
|
|
105
|
+
deploy_wsid = str(uuid.uuid4())
|
|
106
|
+
|
|
107
|
+
try:
|
|
108
|
+
from app.colab_cli.runtime import ColabRuntime
|
|
109
|
+
_worker_bytes = open(os.path.join(os.path.dirname(__file__), "../colab/worker.py"), "rb").read()
|
|
110
|
+
_worker_b64 = base64.b64encode(_worker_bytes).decode()
|
|
111
|
+
|
|
112
|
+
_rt = ColabRuntime(url, token, session_name=name)
|
|
113
|
+
_deploy_code = f"""
|
|
114
|
+
import subprocess, sys, os, json, base64
|
|
115
|
+
subprocess.run([sys.executable, "-m", "pip", "install", "-q", "websockets", "httpx", "soundfile", "pydub", "omnivoice", "--no-deps", "transformers>=4.44.0"], capture_output=True, timeout=120)
|
|
116
|
+
print("DEPS_OK", flush=True)
|
|
117
|
+
worker_path = "/content/worker.py"
|
|
118
|
+
with open(worker_path, "wb") as f:
|
|
119
|
+
f.write(base64.b64decode("{_worker_b64}"))
|
|
120
|
+
env = os.environ.copy()
|
|
121
|
+
env["OMNIVOICE_NUM_STEP"] = "16"
|
|
122
|
+
env["OMNIVOICE_GUIDANCE_SCALE"] = "4.0"
|
|
123
|
+
proc = subprocess.Popen([sys.executable, worker_path,
|
|
124
|
+
"--server-url", "{public_url}",
|
|
125
|
+
"--email", "{email}",
|
|
126
|
+
"--worker-session-id", "{deploy_wsid}"], env=env, start_new_session=True,
|
|
127
|
+
stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
|
128
|
+
import time
|
|
129
|
+
time.sleep(10)
|
|
130
|
+
ret = proc.poll()
|
|
131
|
+
print("DEPLOYED PID=" + str(proc.pid) + " ALIVE=" + str(ret is None), flush=True)
|
|
132
|
+
"""
|
|
133
|
+
await loop.run_in_executor(None, lambda: _rt.execute_code(_deploy_code, timeout=300))
|
|
134
|
+
logger.info("Worker deployed on Colab runtime for %s", email)
|
|
135
|
+
except Exception as e:
|
|
136
|
+
logger.error("Worker deploy failed: %s", e)
|
|
137
|
+
await report_status(email, "FAILED", error=str(e))
|
|
138
|
+
return
|
|
139
|
+
|
|
140
|
+
await report_status(email, "READY", colab_pid=pid, worker_session_id=deploy_wsid)
|
|
141
|
+
running_workers[email] = {"pid": pid, "start_time": time.time()}
|
|
142
|
+
|
|
143
|
+
running_workers = {}
|
|
144
|
+
|
|
145
|
+
async def monitor_workers():
|
|
146
|
+
while True:
|
|
147
|
+
now = time.time()
|
|
148
|
+
for email, info in list(running_workers.items()):
|
|
149
|
+
if now - info["start_time"] > 9000: # 2.5 hours
|
|
150
|
+
logger.info("Auto-rotating worker for %s (reached 2.5h limit)", email)
|
|
151
|
+
try:
|
|
152
|
+
os.kill(info["pid"], signal.SIGTERM)
|
|
153
|
+
except Exception as e:
|
|
154
|
+
logger.debug("Failed to kill PID %s: %s", info["pid"], e)
|
|
155
|
+
|
|
156
|
+
await report_status(email, "FAILED", error="Auto-rotation limit reached")
|
|
157
|
+
del running_workers[email]
|
|
158
|
+
|
|
159
|
+
await asyncio.sleep(60)
|
|
160
|
+
|
|
161
|
+
async def main():
|
|
162
|
+
logger.info("Starting Satellite Daemon %s", NODE_ID)
|
|
163
|
+
logger.info("Master URL: %s", MASTER_URL)
|
|
164
|
+
|
|
165
|
+
asyncio.create_task(monitor_workers())
|
|
166
|
+
|
|
167
|
+
async with httpx.AsyncClient() as client:
|
|
168
|
+
while True:
|
|
169
|
+
try:
|
|
170
|
+
res = await client.get(f"{MASTER_URL}/api/node/jobs", headers=HEADERS)
|
|
171
|
+
if res.status_code == 200:
|
|
172
|
+
job = res.json().get("job")
|
|
173
|
+
if job:
|
|
174
|
+
email = job["email"]
|
|
175
|
+
public_url = job.get("public_url") or MASTER_URL
|
|
176
|
+
# Launch in background so we don't block polling
|
|
177
|
+
asyncio.create_task(launch_worker(email, public_url))
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.debug("Polling error: %s", e)
|
|
180
|
+
|
|
181
|
+
await asyncio.sleep(5)
|
|
182
|
+
|
|
183
|
+
if __name__ == "__main__":
|
|
184
|
+
asyncio.run(main())
|
package/package.json
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ttsd-colabcli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Satellite Node CLI for TTS Dubbing Distributed Architecture",
|
|
5
|
+
"main": "cli.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"colabcli": "./cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"start": "node cli.js"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"cli",
|
|
14
|
+
"tts",
|
|
15
|
+
"worker"
|
|
16
|
+
],
|
|
17
|
+
"author": "",
|
|
18
|
+
"license": "ISC",
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"inquirer": "^8.2.5"
|
|
21
|
+
}
|
|
22
|
+
}
|