Spaces:
Sleeping
Sleeping
Upload Dockerfile
Browse files- Dockerfile +119 -684
Dockerfile
CHANGED
|
@@ -1,684 +1,119 @@
|
|
| 1 |
-
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 2 |
-
# Chat2API โ HuggingFace Space ็
|
| 3 |
-
# ๅบไบ Node.js๏ผ้ๆ Chat2API ้กน็ฎ + code-server ๆ้ๅฏๅ IDE
|
| 4 |
-
# ๆไน
ๅๅญๅจ๏ผHuggingFace Dataset๏ผๆฏๅฐๆถ่ชๅจๅคไปฝ๏ผ้ๅฏ่ชๅจๆขๅค๏ผ
|
| 5 |
-
#
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
#
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
# โโ 1.
|
| 16 |
-
RUN
|
| 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 |
-
path_or_fileobj=io.BytesIO(b"initialized\n"),
|
| 121 |
-
path_in_repo=INIT_FLAG,
|
| 122 |
-
repo_id=repo_id,
|
| 123 |
-
repo_type="dataset",
|
| 124 |
-
token=token,
|
| 125 |
-
commit_message="Create initialized.flag on first deploy",
|
| 126 |
-
)
|
| 127 |
-
print("initialized.flag created in Dataset.")
|
| 128 |
-
return
|
| 129 |
-
|
| 130 |
-
if force_restore:
|
| 131 |
-
print("Restore: FORCE_RESTORE=true, ignoring initialized.flag.")
|
| 132 |
-
else:
|
| 133 |
-
print("Restore: initialized.flag found, normal restart.")
|
| 134 |
-
|
| 135 |
-
skip_set = _parse_skip_list("RESTORE_SKIP")
|
| 136 |
-
|
| 137 |
-
try:
|
| 138 |
-
now = datetime.now()
|
| 139 |
-
for i in range(5):
|
| 140 |
-
day = (now - timedelta(days=i)).strftime("%Y-%m-%d")
|
| 141 |
-
name = f"backup_{day}.tar.gz"
|
| 142 |
-
if name in all_files:
|
| 143 |
-
print(f"Downloading {name}...")
|
| 144 |
-
path = hf_hub_download(repo_id=repo_id, filename=name, repo_type="dataset", token=token)
|
| 145 |
-
os.makedirs(DATA_DIR, exist_ok=True)
|
| 146 |
-
with tarfile.open(path, "r:gz") as tar:
|
| 147 |
-
for member in tar.getmembers():
|
| 148 |
-
if _is_skipped(member.name, skip_set):
|
| 149 |
-
print(f"Restore skip: {member.name}")
|
| 150 |
-
continue
|
| 151 |
-
tar.extract(member, path=DATA_DIR)
|
| 152 |
-
print(f"Restored from {name}")
|
| 153 |
-
break
|
| 154 |
-
|
| 155 |
-
import io
|
| 156 |
-
api.upload_file(
|
| 157 |
-
path_or_fileobj=io.BytesIO(b"initialized at container startup\n"),
|
| 158 |
-
path_in_repo=INIT_FLAG,
|
| 159 |
-
repo_id=repo_id,
|
| 160 |
-
repo_type="dataset",
|
| 161 |
-
token=token,
|
| 162 |
-
commit_message="Set initialized.flag",
|
| 163 |
-
)
|
| 164 |
-
print("initialized.flag updated.")
|
| 165 |
-
except Exception as e:
|
| 166 |
-
print(f"Restore Error: {e}")
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
# โโ backup() โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 170 |
-
|
| 171 |
-
def backup():
|
| 172 |
-
if not repo_id or not token:
|
| 173 |
-
print("Skip Backup: HF_DATASET or HF_TOKEN not set")
|
| 174 |
-
return
|
| 175 |
-
|
| 176 |
-
backup_tar_skip_raw = os.getenv("BACKUP_TAR_SKIP", "").strip()
|
| 177 |
-
if backup_tar_skip_raw == "all":
|
| 178 |
-
print("Backup skip: BACKUP_TAR_SKIP=all")
|
| 179 |
-
return
|
| 180 |
-
try:
|
| 181 |
-
tar_skip_set = _parse_skip_list("BACKUP_TAR_SKIP")
|
| 182 |
-
day = datetime.now().strftime("%Y-%m-%d")
|
| 183 |
-
name = f"backup_{day}.tar.gz"
|
| 184 |
-
with tarfile.open(name, "w:gz") as tar:
|
| 185 |
-
for abs_path, rel_to_base in _walk_local(DATA_DIR, skip_set=tar_skip_set):
|
| 186 |
-
tar.add(abs_path, arcname=rel_to_base)
|
| 187 |
-
api.upload_file(path_or_fileobj=name, path_in_repo=name, repo_id=repo_id, repo_type="dataset", token=token)
|
| 188 |
-
print(f"Backup {name} done.")
|
| 189 |
-
except Exception as e:
|
| 190 |
-
print(f"Backup Error: {e}")
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
if __name__ == "__main__":
|
| 194 |
-
if len(sys.argv) > 1 and sys.argv[1] == "backup":
|
| 195 |
-
backup()
|
| 196 |
-
else:
|
| 197 |
-
restore()
|
| 198 |
-
EOF
|
| 199 |
-
|
| 200 |
-
# โโ 8. ๅๅ
ฅ cs-manager ๅฎๆค่ฟ็จ๏ผๆฅ่ชๅ็ Dockerfile๏ผๅๆ ทๅคๅถ๏ผ โโโโโโโโโโโโโโโโ
|
| 201 |
-
RUN cat <<'PYEOF' > /usr/local/bin/cs-manager
|
| 202 |
-
#!/usr/bin/env python3
|
| 203 |
-
"""
|
| 204 |
-
cs-manager: code-server ๆ้ๅฏๅๅฎๆค่ฟ็จ
|
| 205 |
-
|
| 206 |
-
็ๅฌ Unix socket๏ผๆไพไธคไธช HTTP ็ซฏ็น๏ผ
|
| 207 |
-
GET /wakeup - ่งฆๅๅฏๅจ code-server๏ผ่ฅๆช่ฟ่ก๏ผ๏ผ่ฟๅ"ๅฏๅจไธญ"็ญๅพ
้กต
|
| 208 |
-
GET /heartbeat - ๆดๆฐๆๅๆดป่ทๆถ้ด๏ผnginx ๆฏๆฌกๆๅไปฃ็ /ide/ ๅ่ฐ็จ๏ผ
|
| 209 |
-
|
| 210 |
-
ๅๅฐๅฎๆถไปปๅก๏ผ
|
| 211 |
-
ๆฏ 60 ็งๆฃๆฅไธๆฌก๏ผ่ฅ่ถ
่ฟ IDE_IDLE_MINUTES ๅ้ๆ heartbeat๏ผkill code-server
|
| 212 |
-
"""
|
| 213 |
-
|
| 214 |
-
import os, sys, time, signal, subprocess, threading, socket, re
|
| 215 |
-
from http.server import HTTPServer, BaseHTTPRequestHandler
|
| 216 |
-
|
| 217 |
-
# โโ ้
็ฝฎ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 218 |
-
SOCK_PATH = "/tmp/cs-manager.sock"
|
| 219 |
-
CS_PORT = int(os.environ.get("CODE_SERVER_PORT", "13337"))
|
| 220 |
-
CS_PASSWORD = os.environ.get("CODE_SERVER_PASSWORD", "changeme123!")
|
| 221 |
-
CS_USER_DATA_DIR = "/root/.code-server"
|
| 222 |
-
CS_EXTENSIONS_DIR= "/root/.code-server/extensions"
|
| 223 |
-
CS_WORKSPACE = "/root/.chat2api"
|
| 224 |
-
CS_LOG = "/root/.chat2api/logs/code-server.log"
|
| 225 |
-
IDLE_MINUTES = int(os.environ.get("IDE_IDLE_MINUTES", "30"))
|
| 226 |
-
LAST_ACCESS_FILE = "/tmp/cs-last-access"
|
| 227 |
-
CS_PID_FILE = "/tmp/cs-server.pid"
|
| 228 |
-
CHECK_INTERVAL = 60 # ็ง
|
| 229 |
-
|
| 230 |
-
# โโ ๅ
จๅฑ็ถๆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 231 |
-
_lock = threading.Lock()
|
| 232 |
-
_starting = False # ๆญฃๅจๅฏๅจไธญ๏ผ้ฒๆญขๅนถๅ้ๅคๅฏๅจ
|
| 233 |
-
|
| 234 |
-
# โโ ๅทฅๅ
ทๅฝๆฐ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 235 |
-
|
| 236 |
-
def log(msg):
|
| 237 |
-
ts = time.strftime("%Y-%m-%d %H:%M:%S")
|
| 238 |
-
print(f"[cs-manager {ts}] {msg}", flush=True)
|
| 239 |
-
|
| 240 |
-
def touch_last_access():
|
| 241 |
-
try:
|
| 242 |
-
with open(LAST_ACCESS_FILE, "w") as f:
|
| 243 |
-
f.write(str(time.time()))
|
| 244 |
-
os.utime(LAST_ACCESS_FILE, None)
|
| 245 |
-
except Exception as e:
|
| 246 |
-
log(f"touch_last_access error: {e}")
|
| 247 |
-
|
| 248 |
-
def get_cs_pid():
|
| 249 |
-
"""่ฏปๅ PID ๆไปถ๏ผ่ฟๅ code-server PID๏ผ่ฅ่ฟ็จๅญๅจ๏ผ๏ผๅฆๅ่ฟๅ None"""
|
| 250 |
-
try:
|
| 251 |
-
with open(CS_PID_FILE) as f:
|
| 252 |
-
pid = int(f.read().strip())
|
| 253 |
-
os.kill(pid, 0) # ๆขๆต่ฟ็จๆฏๅฆๅญๅจ
|
| 254 |
-
return pid
|
| 255 |
-
except Exception:
|
| 256 |
-
return None
|
| 257 |
-
|
| 258 |
-
def is_cs_running():
|
| 259 |
-
return get_cs_pid() is not None
|
| 260 |
-
|
| 261 |
-
def is_cs_port_ready():
|
| 262 |
-
"""ๅฐ่ฏ TCP ่ฟๆฅ code-server ็ซฏๅฃ๏ผ็กฎ่ฎคๆๅก็ๆญฃๅฏ็จ"""
|
| 263 |
-
try:
|
| 264 |
-
s = socket.create_connection(("127.0.0.1", CS_PORT), timeout=1)
|
| 265 |
-
s.close()
|
| 266 |
-
return True
|
| 267 |
-
except Exception:
|
| 268 |
-
return False
|
| 269 |
-
|
| 270 |
-
def start_cs():
|
| 271 |
-
"""ๅฏๅจ code-server๏ผ้้ปๅก๏ผ่ฟๅๅ่ฟ็จๅจๅๅฐ่ฟ่ก"""
|
| 272 |
-
global _starting
|
| 273 |
-
with _lock:
|
| 274 |
-
if _starting or is_cs_running():
|
| 275 |
-
return
|
| 276 |
-
_starting = True
|
| 277 |
-
|
| 278 |
-
try:
|
| 279 |
-
log(f"Starting code-server on port {CS_PORT}...")
|
| 280 |
-
os.makedirs(os.path.dirname(CS_LOG), exist_ok=True)
|
| 281 |
-
os.makedirs(CS_USER_DATA_DIR, exist_ok=True)
|
| 282 |
-
|
| 283 |
-
# ๅๅ
ฅ code-server config.yaml
|
| 284 |
-
cfg_dir = "/root/.config/code-server"
|
| 285 |
-
os.makedirs(cfg_dir, exist_ok=True)
|
| 286 |
-
with open(os.path.join(cfg_dir, "config.yaml"), "w") as f:
|
| 287 |
-
f.write(f"bind-addr: 127.0.0.1:{CS_PORT}\n")
|
| 288 |
-
f.write(f"auth: password\n")
|
| 289 |
-
f.write(f"password: {CS_PASSWORD}\n")
|
| 290 |
-
f.write(f"cert: false\n")
|
| 291 |
-
|
| 292 |
-
env = os.environ.copy()
|
| 293 |
-
env.pop("PORT", None) # ้ฒๆญข code-server ่ฏปๅ PORT=7860 ่ฆ็็ซฏๅฃ
|
| 294 |
-
|
| 295 |
-
log_file = open(CS_LOG, "a")
|
| 296 |
-
proc = subprocess.Popen(
|
| 297 |
-
[
|
| 298 |
-
"code-server",
|
| 299 |
-
"--disable-telemetry",
|
| 300 |
-
"--disable-update-check",
|
| 301 |
-
f"--user-data-dir={CS_USER_DATA_DIR}",
|
| 302 |
-
f"--extensions-dir={CS_EXTENSIONS_DIR}",
|
| 303 |
-
CS_WORKSPACE,
|
| 304 |
-
],
|
| 305 |
-
stdout=log_file,
|
| 306 |
-
stderr=log_file,
|
| 307 |
-
env=env,
|
| 308 |
-
start_new_session=True, # ่ฑ็ฆปๅฝๅ่ฟ็จ็ป๏ผ้ฟๅ
้็ถ่ฟ็จ็ปๆญข
|
| 309 |
-
)
|
| 310 |
-
|
| 311 |
-
# ๅๅ
ฅ PID ๆไปถ
|
| 312 |
-
with open(CS_PID_FILE, "w") as f:
|
| 313 |
-
f.write(str(proc.pid))
|
| 314 |
-
log(f"code-server started, PID={proc.pid}")
|
| 315 |
-
touch_last_access()
|
| 316 |
-
except Exception as e:
|
| 317 |
-
log(f"start_cs error: {e}")
|
| 318 |
-
finally:
|
| 319 |
-
with _lock:
|
| 320 |
-
_starting = False
|
| 321 |
-
|
| 322 |
-
def stop_cs():
|
| 323 |
-
"""็ปๆญข code-server ่ฟ็จ"""
|
| 324 |
-
pid = get_cs_pid()
|
| 325 |
-
if pid is None:
|
| 326 |
-
log("stop_cs: code-server not running, skip")
|
| 327 |
-
return
|
| 328 |
-
try:
|
| 329 |
-
os.kill(pid, signal.SIGTERM)
|
| 330 |
-
log(f"code-server (PID={pid}) terminated (SIGTERM)")
|
| 331 |
-
except Exception as e:
|
| 332 |
-
log(f"stop_cs error: {e}")
|
| 333 |
-
try:
|
| 334 |
-
os.remove(CS_PID_FILE)
|
| 335 |
-
except Exception:
|
| 336 |
-
pass
|
| 337 |
-
|
| 338 |
-
# โโ ้ฒ็ฝฎๆฃๆตๅฎๆถๅจ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 339 |
-
|
| 340 |
-
def idle_checker():
|
| 341 |
-
while True:
|
| 342 |
-
time.sleep(CHECK_INTERVAL)
|
| 343 |
-
try:
|
| 344 |
-
if not is_cs_running():
|
| 345 |
-
continue
|
| 346 |
-
try:
|
| 347 |
-
mtime = os.path.getmtime(LAST_ACCESS_FILE)
|
| 348 |
-
except FileNotFoundError:
|
| 349 |
-
mtime = 0
|
| 350 |
-
idle_secs = time.time() - mtime
|
| 351 |
-
idle_mins = idle_secs / 60
|
| 352 |
-
if idle_mins >= IDLE_MINUTES:
|
| 353 |
-
log(f"Idle for {idle_mins:.1f} min (threshold={IDLE_MINUTES} min), stopping code-server...")
|
| 354 |
-
stop_cs()
|
| 355 |
-
else:
|
| 356 |
-
remaining = IDLE_MINUTES - idle_mins
|
| 357 |
-
log(f"code-server running, idle {idle_mins:.1f}/{IDLE_MINUTES} min (auto-stop in {remaining:.1f} min)")
|
| 358 |
-
except Exception as e:
|
| 359 |
-
log(f"idle_checker error: {e}")
|
| 360 |
-
|
| 361 |
-
# โโ HTTP ่ฏทๆฑๅค็ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 362 |
-
|
| 363 |
-
WAKEUP_HTML = """\
|
| 364 |
-
<!DOCTYPE html>
|
| 365 |
-
<html lang="zh">
|
| 366 |
-
<head>
|
| 367 |
-
<meta charset="utf-8">
|
| 368 |
-
<meta http-equiv="refresh" content="5;url=/ide/">
|
| 369 |
-
<title>IDE ๅฏๅจไธญ...</title>
|
| 370 |
-
<style>
|
| 371 |
-
body {{ font-family: sans-serif; display:flex; align-items:center;
|
| 372 |
-
justify-content:center; height:100vh; margin:0; background:#1e1e1e; color:#ccc; }}
|
| 373 |
-
.box {{ text-align:center; }}
|
| 374 |
-
.spinner {{ width:48px; height:48px; border:5px solid #555;
|
| 375 |
-
border-top-color:#0078d4; border-radius:50%;
|
| 376 |
-
animation:spin 1s linear infinite; margin:0 auto 20px; }}
|
| 377 |
-
@keyframes spin {{ to {{ transform:rotate(360deg) }} }}
|
| 378 |
-
p {{ margin:6px 0; }}
|
| 379 |
-
small {{ color:#888; }}
|
| 380 |
-
</style>
|
| 381 |
-
</head>
|
| 382 |
-
<body>
|
| 383 |
-
<div class="box">
|
| 384 |
-
<div class="spinner"></div>
|
| 385 |
-
<p>VS Code IDE ๆญฃๅจๅฏๅจ๏ผ่ฏท็จๅ...</p>
|
| 386 |
-
<p><small>้กต้ขๅฐๅจ 5 ็งๅ่ชๅจ้่ฏ๏ผๆๆๅจ <a href="/ide/" style="color:#0078d4">ๅทๆฐ</a></small></p>
|
| 387 |
-
</div>
|
| 388 |
-
</body>
|
| 389 |
-
</html>
|
| 390 |
-
"""
|
| 391 |
-
|
| 392 |
-
class CSManagerHandler(BaseHTTPRequestHandler):
|
| 393 |
-
def log_message(self, fmt, *args):
|
| 394 |
-
pass # ้้ป access log๏ผ้ฟๅ
ๅทๆฅๅฟ
|
| 395 |
-
|
| 396 |
-
def do_GET(self):
|
| 397 |
-
if self.path.startswith("/wakeup"):
|
| 398 |
-
self._handle_wakeup()
|
| 399 |
-
elif self.path.startswith("/heartbeat"):
|
| 400 |
-
self._handle_heartbeat()
|
| 401 |
-
else:
|
| 402 |
-
self.send_response(404)
|
| 403 |
-
self.end_headers()
|
| 404 |
-
|
| 405 |
-
def _handle_wakeup(self):
|
| 406 |
-
"""
|
| 407 |
-
nginx @ide_wakeup ่ฐ็จๆญค็ซฏ็นใ
|
| 408 |
-
่ฅ code-server ๅทฒๅจ่ฟ่ก๏ผ็ซฏๅฃๅฏ่พพ๏ผ๏ผ่ฟๅ 302 ้ๅฎๅๅ /ide/ใ
|
| 409 |
-
่ฅๆช่ฟ่ก๏ผ่งฆๅๅๅฐๅฏๅจ๏ผ่ฟๅ 200 "ๅฏๅจไธญ"็ญๅพ
้กตใ
|
| 410 |
-
"""
|
| 411 |
-
if is_cs_port_ready():
|
| 412 |
-
touch_last_access()
|
| 413 |
-
self.send_response(302)
|
| 414 |
-
self.send_header("Location", "/ide/")
|
| 415 |
-
self.end_headers()
|
| 416 |
-
else:
|
| 417 |
-
if not is_cs_running():
|
| 418 |
-
t = threading.Thread(target=start_cs, daemon=True)
|
| 419 |
-
t.start()
|
| 420 |
-
html = WAKEUP_HTML.encode()
|
| 421 |
-
self.send_response(200)
|
| 422 |
-
self.send_header("Content-Type", "text/html; charset=utf-8")
|
| 423 |
-
self.send_header("Content-Length", str(len(html)))
|
| 424 |
-
self.end_headers()
|
| 425 |
-
self.wfile.write(html)
|
| 426 |
-
|
| 427 |
-
def _handle_heartbeat(self):
|
| 428 |
-
"""nginx ๆๅ่ฝฌๅ /ide/ ่ฏทๆฑๅ่ฐ็จ๏ผๆดๆฐๆดป่ทๆถ้ด"""
|
| 429 |
-
touch_last_access()
|
| 430 |
-
self.send_response(204)
|
| 431 |
-
self.end_headers()
|
| 432 |
-
|
| 433 |
-
|
| 434 |
-
class UnixSocketHTTPServer(HTTPServer):
|
| 435 |
-
"""ๅจ Unix domain socket ไธ็ๅฌ็ HTTPServer"""
|
| 436 |
-
address_family = socket.AF_UNIX
|
| 437 |
-
|
| 438 |
-
def server_bind(self):
|
| 439 |
-
try:
|
| 440 |
-
os.unlink(self.server_address)
|
| 441 |
-
except FileNotFoundError:
|
| 442 |
-
pass
|
| 443 |
-
super().server_bind()
|
| 444 |
-
os.chmod(self.server_address, 0o666)
|
| 445 |
-
|
| 446 |
-
|
| 447 |
-
# โโ ไธปๅ
ฅๅฃ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 448 |
-
|
| 449 |
-
if __name__ == "__main__":
|
| 450 |
-
log(f"cs-manager starting (idle_timeout={IDLE_MINUTES} min, cs_port={CS_PORT})")
|
| 451 |
-
log(f"Listening on Unix socket: {SOCK_PATH}")
|
| 452 |
-
|
| 453 |
-
# ๅฏๅจ้ฒ็ฝฎๆฃๆตๅๅฐ็บฟ็จ
|
| 454 |
-
t = threading.Thread(target=idle_checker, daemon=True)
|
| 455 |
-
t.start()
|
| 456 |
-
|
| 457 |
-
# ๅฏๅจ HTTP ๆๅก๏ผUnix socket๏ผ
|
| 458 |
-
server = UnixSocketHTTPServer(SOCK_PATH, CSManagerHandler)
|
| 459 |
-
try:
|
| 460 |
-
server.serve_forever()
|
| 461 |
-
except KeyboardInterrupt:
|
| 462 |
-
log("cs-manager shutting down")
|
| 463 |
-
server.server_close()
|
| 464 |
-
PYEOF
|
| 465 |
-
|
| 466 |
-
RUN chmod +x /usr/local/bin/cs-manager
|
| 467 |
-
|
| 468 |
-
# โโ 9. ๅๅ
ฅไธปๅฏๅจ่ๆฌ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 469 |
-
RUN cat <<'EOF' > /usr/local/bin/start-chat2api
|
| 470 |
-
#!/bin/bash
|
| 471 |
-
set -e
|
| 472 |
-
|
| 473 |
-
# โโ ็ฏๅขๅ้ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ๏ฟฝ๏ฟฝโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 474 |
-
LISTEN_PORT="${PORT:-7860}"
|
| 475 |
-
CHAT2API_PORT=7862 # Chat2API ๆๅกๅ
้จ็ซฏๅฃ
|
| 476 |
-
CODE_SERVER_PORT="${CODE_SERVER_PORT:-13337}"
|
| 477 |
-
IDE_IDLE_MINUTES="${IDE_IDLE_MINUTES:-30}"
|
| 478 |
-
|
| 479 |
-
export CODE_SERVER_PORT IDE_IDLE_MINUTES
|
| 480 |
-
export CODE_SERVER_PASSWORD="${CODE_SERVER_PASSWORD:-changeme123!}"
|
| 481 |
-
export CHAT2API_DATA_DIR="/root/.chat2api"
|
| 482 |
-
|
| 483 |
-
echo "=== Chat2API HuggingFace Space ๅฏๅจ ==="
|
| 484 |
-
echo "ๅค้จ็ๅฌ็ซฏๅฃ : ${LISTEN_PORT}"
|
| 485 |
-
echo "Chat2APIๅ
้จ็ซฏๅฃ: ${CHAT2API_PORT}"
|
| 486 |
-
echo "IDEๅ
้จ็ซฏๅฃ : ${CODE_SERVER_PORT}"
|
| 487 |
-
echo "IDE้ฒ็ฝฎ่ชๅจๅ
ณ้ญ : ${IDE_IDLE_MINUTES} min"
|
| 488 |
-
|
| 489 |
-
# โโ ๆญฅ้ชค 1๏ผไป HF Dataset ๆขๅคๆไน
ๅๆฐๆฎ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 490 |
-
echo "--- ๆขๅคๆไน
ๅ้
็ฝฎๆฐๆฎ ---"
|
| 491 |
-
python3 /usr/local/bin/sync.py restore
|
| 492 |
-
|
| 493 |
-
# โโ ๆญฅ้ชค 2๏ผๅๅค Chat2API ๆฐๆฎ็ฎๅฝ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 494 |
-
mkdir -p "${CHAT2API_DATA_DIR}/logs" "${CHAT2API_DATA_DIR}/sessions"
|
| 495 |
-
|
| 496 |
-
# ่ฅๆฐๆฎ็ฎๅฝไธๅญๅจ้
็ฝฎ๏ผไป HF Space ๅ้็ๆๅๅง้
็ฝฎ
|
| 497 |
-
CONFIG_FILE="${CHAT2API_DATA_DIR}/config.json"
|
| 498 |
-
if [ ! -f "$CONFIG_FILE" ]; then
|
| 499 |
-
echo "็ๆๅๅง config.json..."
|
| 500 |
-
python3 - << 'PYEOF'
|
| 501 |
-
import os, json
|
| 502 |
-
|
| 503 |
-
config = {
|
| 504 |
-
"port": int(os.getenv("CHAT2API_PORT", "7862")),
|
| 505 |
-
"apiKey": os.getenv("CHAT2API_API_KEY", ""),
|
| 506 |
-
"loadBalance": {
|
| 507 |
-
"strategy": os.getenv("LB_STRATEGY", "round-robin")
|
| 508 |
-
}
|
| 509 |
-
}
|
| 510 |
-
os.makedirs(os.getenv("CHAT2API_DATA_DIR", "/root/.chat2api"), exist_ok=True)
|
| 511 |
-
with open(os.path.join(os.getenv("CHAT2API_DATA_DIR", "/root/.chat2api"), "config.json"), "w") as f:
|
| 512 |
-
json.dump(config, f, indent=2)
|
| 513 |
-
print("config.json initialized.")
|
| 514 |
-
PYEOF
|
| 515 |
-
fi
|
| 516 |
-
|
| 517 |
-
# ่ฅๆฐๆฎ็ฎๅฝไธๅญๅจ่ดฆๆทๅ่กจ๏ผไป HF Space ๅ้ๆณจๅ
ฅๅๅง่ดฆๆท
|
| 518 |
-
ACCOUNTS_FILE="${CHAT2API_DATA_DIR}/accounts.json"
|
| 519 |
-
if [ ! -f "$ACCOUNTS_FILE" ]; then
|
| 520 |
-
echo "็ๆๅๅง accounts.json..."
|
| 521 |
-
python3 - << 'PYEOF'
|
| 522 |
-
import os, json
|
| 523 |
-
|
| 524 |
-
accounts = []
|
| 525 |
-
# ไป็ฏๅขๅ้ PROVIDER_x_TOKEN ๆน้ๆณจๅ
ฅ่ดฆๆท
|
| 526 |
-
# ๆ ผๅผ: PROVIDER_1_TYPE=deepseek, PROVIDER_1_TOKEN=xxx
|
| 527 |
-
for i in range(1, 11):
|
| 528 |
-
ptype = os.getenv(f"PROVIDER_{i}_TYPE", "").strip()
|
| 529 |
-
token = os.getenv(f"PROVIDER_{i}_TOKEN", "").strip()
|
| 530 |
-
if ptype and token:
|
| 531 |
-
accounts.append({"provider": ptype, "token": token, "enabled": True})
|
| 532 |
-
|
| 533 |
-
data_dir = os.getenv("CHAT2API_DATA_DIR", "/root/.chat2api")
|
| 534 |
-
with open(os.path.join(data_dir, "accounts.json"), "w") as f:
|
| 535 |
-
json.dump(accounts, f, indent=2)
|
| 536 |
-
print(f"accounts.json initialized with {len(accounts)} account(s).")
|
| 537 |
-
PYEOF
|
| 538 |
-
fi
|
| 539 |
-
|
| 540 |
-
# โโ ๆญฅ้ชค 3๏ผๅฏๅจ Chat2API ๆๅก๏ผๅๅฐ๏ผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 541 |
-
echo "--- ๅฏๅจ Chat2API ๆๅก (ๅ
้จ็ซฏๅฃ ${CHAT2API_PORT}) ---"
|
| 542 |
-
cd /app/chat2api
|
| 543 |
-
PORT=${CHAT2API_PORT} \
|
| 544 |
-
CHAT2API_DATA="${CHAT2API_DATA_DIR}" \
|
| 545 |
-
npx electron-vite dev 2>&1 | tee "${CHAT2API_DATA_DIR}/logs/chat2api.log" &
|
| 546 |
-
echo "Chat2API ๅทฒๅจๅๅฐๅฏๅจ"
|
| 547 |
-
|
| 548 |
-
# โโ ๆญฅ้ชค 4๏ผๅฏๅจ cs-manager ๅฎๆค่ฟ็จ๏ผๅๅฐ๏ผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 549 |
-
echo "--- ๅฏๅจ IDE ๆ้็ฎก็ๅจ ---"
|
| 550 |
-
python3 /usr/local/bin/cs-manager 2>&1 | tee "${CHAT2API_DATA_DIR}/logs/cs-manager.log" &
|
| 551 |
-
|
| 552 |
-
# ็ญๅพ
cs-manager socket ๅฐฑ็ปช๏ผๆๅค 10 ็ง๏ผ
|
| 553 |
-
for i in $(seq 1 20); do
|
| 554 |
-
if [ -S /tmp/cs-manager.sock ]; then
|
| 555 |
-
echo "cs-manager socket ๅฐฑ็ปช"
|
| 556 |
-
break
|
| 557 |
-
fi
|
| 558 |
-
sleep 0.5
|
| 559 |
-
done
|
| 560 |
-
|
| 561 |
-
# โโ ๆญฅ้ชค 5๏ผ็ญๅพ
Chat2API ๆๅกๅฐฑ็ปช๏ผๆๅค 120 ็ง๏ผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 562 |
-
echo "็ญๅพ
Chat2API ๆๅกๅฏๅจ..."
|
| 563 |
-
for i in $(seq 1 60); do
|
| 564 |
-
if curl -fsS http://127.0.0.1:${CHAT2API_PORT}/ >/dev/null 2>&1; then
|
| 565 |
-
echo "Chat2API ๆๅกๅทฒๅฐฑ็ปช๏ผ${i}*2s๏ผ"
|
| 566 |
-
break
|
| 567 |
-
fi
|
| 568 |
-
sleep 2
|
| 569 |
-
done
|
| 570 |
-
|
| 571 |
-
# โโ ๆญฅ้ชค 6๏ผๅฏๅจๅฎๆถๅคไปฝๅพช็ฏ๏ผๆฏ 60 ๅ้๏ผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 572 |
-
(while true; do sleep 3600; python3 /usr/local/bin/sync.py backup; done) &
|
| 573 |
-
echo "ๆไน
ๅๅคไปฝๅพช็ฏๅทฒๅฏๅจ๏ผๆฏๅฐๆถๅคไปฝไธๆฌก๏ผ"
|
| 574 |
-
|
| 575 |
-
# โโ ๆญฅ้ชค 7๏ผ็ๆ nginx ้
็ฝฎๅนถๅฏๅจ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 576 |
-
rm -f /etc/nginx/sites-enabled/default /etc/nginx/conf.d/default.conf
|
| 577 |
-
|
| 578 |
-
cat > /etc/nginx/conf.d/chat2api.conf <<NGINX
|
| 579 |
-
# cs-manager Unix socket upstream๏ผIDE ๆ้ๅฏๅๆงๅถๅจ๏ผ
|
| 580 |
-
upstream cs_manager {
|
| 581 |
-
server unix:/tmp/cs-manager.sock;
|
| 582 |
-
}
|
| 583 |
-
|
| 584 |
-
server {
|
| 585 |
-
listen PLACEHOLDER_LISTEN_PORT;
|
| 586 |
-
server_name _;
|
| 587 |
-
client_max_body_size 100M;
|
| 588 |
-
|
| 589 |
-
access_log /dev/stdout;
|
| 590 |
-
error_log /dev/stderr warn;
|
| 591 |
-
|
| 592 |
-
# HF Space ็ฑ Cloudflare ๅ SSL ็ป็ป๏ผ้ฟๅ
็ซฏๅฃๅทๅบ็ฐๅจ้ๅฎๅ URL ไธญ
|
| 593 |
-
absolute_redirect off;
|
| 594 |
-
port_in_redirect off;
|
| 595 |
-
|
| 596 |
-
# โโ /ide/ ไธป location๏ผIDE ๆ้ๅฏๅ๏ผโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 597 |
-
location /ide/ {
|
| 598 |
-
proxy_pass http://127.0.0.1:PLACEHOLDER_CODE_SERVER_PORT/;
|
| 599 |
-
proxy_http_version 1.1;
|
| 600 |
-
proxy_set_header Host \$host;
|
| 601 |
-
proxy_set_header Upgrade \$http_upgrade;
|
| 602 |
-
proxy_set_header Connection "upgrade";
|
| 603 |
-
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
| 604 |
-
proxy_set_header X-Forwarded-Proto \$scheme;
|
| 605 |
-
proxy_redirect / /ide/;
|
| 606 |
-
proxy_read_timeout 86400;
|
| 607 |
-
proxy_connect_timeout 2s;
|
| 608 |
-
error_page 502 504 @ide_wakeup;
|
| 609 |
-
post_action /ide-heartbeat/;
|
| 610 |
-
}
|
| 611 |
-
|
| 612 |
-
# โโ ๅฟ่ทณ็ซฏ็น โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 613 |
-
location /ide-heartbeat/ {
|
| 614 |
-
internal;
|
| 615 |
-
rewrite ^ /heartbeat break;
|
| 616 |
-
proxy_pass http://cs_manager;
|
| 617 |
-
proxy_connect_timeout 1s;
|
| 618 |
-
proxy_read_timeout 2s;
|
| 619 |
-
}
|
| 620 |
-
|
| 621 |
-
# โโ IDE ๅค้ fallback โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 622 |
-
location @ide_wakeup {
|
| 623 |
-
rewrite ^ /wakeup break;
|
| 624 |
-
proxy_pass http://cs_manager;
|
| 625 |
-
proxy_http_version 1.1;
|
| 626 |
-
proxy_set_header Host \$host;
|
| 627 |
-
proxy_connect_timeout 5s;
|
| 628 |
-
proxy_read_timeout 30s;
|
| 629 |
-
}
|
| 630 |
-
|
| 631 |
-
# โโ ๆๆๅ
ถไป่ฏทๆฑ๏ผ่ฝฌๅๅฐ Chat2API ๆๅก โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 632 |
-
location / {
|
| 633 |
-
proxy_pass http://127.0.0.1:PLACEHOLDER_CHAT2API_PORT/;
|
| 634 |
-
proxy_http_version 1.1;
|
| 635 |
-
proxy_set_header Host \$host;
|
| 636 |
-
proxy_set_header Upgrade \$http_upgrade;
|
| 637 |
-
proxy_set_header Connection "upgrade";
|
| 638 |
-
proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for;
|
| 639 |
-
proxy_set_header X-Forwarded-Proto \$scheme;
|
| 640 |
-
proxy_read_timeout 86400;
|
| 641 |
-
}
|
| 642 |
-
}
|
| 643 |
-
NGINX
|
| 644 |
-
|
| 645 |
-
sed -i \
|
| 646 |
-
"s/PLACEHOLDER_LISTEN_PORT/${LISTEN_PORT}/g;
|
| 647 |
-
s/PLACEHOLDER_CODE_SERVER_PORT/${CODE_SERVER_PORT}/g;
|
| 648 |
-
s/PLACEHOLDER_CHAT2API_PORT/${CHAT2API_PORT}/g" \
|
| 649 |
-
/etc/nginx/conf.d/chat2api.conf
|
| 650 |
-
|
| 651 |
-
echo "nginx ้
็ฝฎๅทฒ็ๆ๏ผ"
|
| 652 |
-
cat /etc/nginx/conf.d/chat2api.conf
|
| 653 |
-
|
| 654 |
-
nginx -t
|
| 655 |
-
echo "ๅฏๅจ nginx๏ผๅๅฐ่ฟ่ก๏ผไฟๆๅฎนๅจๅญๆดป๏ผ..."
|
| 656 |
-
exec nginx -g 'daemon off; error_log /dev/stderr warn;'
|
| 657 |
-
EOF
|
| 658 |
-
|
| 659 |
-
RUN chmod +x /usr/local/bin/start-chat2api
|
| 660 |
-
|
| 661 |
-
# โโ 10. ๆด้ฒ็ซฏๅฃ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 662 |
-
EXPOSE 7860
|
| 663 |
-
|
| 664 |
-
# โโ ่ฎฟ้ฎ่ฏดๆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 665 |
-
# Space ๅฏๅจๅ่ฎฟ้ฎๆนๅผ๏ผ
|
| 666 |
-
# ไธป็้ข (Chat2API ็ฎก็้ขๆฟ) : https://<your-space>.hf.space/
|
| 667 |
-
# API ๆฅๅ
ฅ็ซฏ็น : https://<your-space>.hf.space/v1/
|
| 668 |
-
# VS Code IDE : https://<your-space>.hf.space/ide/
|
| 669 |
-
#
|
| 670 |
-
# ๅฟ
ๅกซ HF Space Secrets๏ผๅ้๏ผ๏ผ
|
| 671 |
-
# HF_TOKEN โโ HuggingFace ่ฎฟ้ฎไปค็๏ผ็จไบ Dataset ่ฏปๅ๏ผ้ๆ write ๆ้๏ผ
|
| 672 |
-
# HF_DATASET โโ Dataset repo ID๏ผๆ ผๅผ๏ผyour-username/your-dataset-name
|
| 673 |
-
#
|
| 674 |
-
# ๅฏ้ Secrets๏ผๅฏๅจ้ขๆฟไธญ้
็ฝฎ๏ผไนๅฏ้่ฟๅ้้ขๆณจๅ
ฅ๏ผ๏ผ
|
| 675 |
-
# CHAT2API_API_KEY โโ ไธบ Chat2API ่ฎพ็ฝฎ่ฎฟ้ฎๅฏ้ฅ๏ผ็็ฉบๅๆ ้่ฎค่ฏ๏ผ
|
| 676 |
-
# CODE_SERVER_PASSWORDโโ VS Code IDE ็ปๅฝๅฏ็ ๏ผ้ป่ฎค๏ผchangeme123!๏ผ
|
| 677 |
-
# IDE_IDLE_MINUTES โโ IDE ้ฒ็ฝฎ่ชๅจๅ
ณ้ญๆถ้ด๏ผๅ้๏ผ๏ผ้ป่ฎค 30
|
| 678 |
-
# PROVIDER_1_TYPE โโ ็ฌฌไธไธชๆๅกๅ็ฑปๅ๏ผๅฆ deepseek / kimi / qwen
|
| 679 |
-
# PROVIDER_1_TOKEN โโ ็ฌฌไธไธชๆๅกๅ็่ฎค่ฏ Token
|
| 680 |
-
# PROVIDER_2_TYPE / PROVIDER_2_TOKEN โโ ็ฌฌไบไธชๆๅกๅ๏ผไปฅๆญค็ฑปๆจ๏ผๆๅค 10 ไธช๏ผ
|
| 681 |
-
# LB_STRATEGY โโ ่ด่ฝฝๅ่กก็ญ็ฅ๏ผround-robin / fill-first / failover
|
| 682 |
-
# FORCE_RESTORE โโ ่ฎพไธบ true ๅผบๅถไป Dataset ่ฆ็ๆขๅค๏ผFactory Reset ็จ๏ผ
|
| 683 |
-
|
| 684 |
-
CMD ["/usr/local/bin/start-chat2api"]
|
|
|
|
| 1 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 2 |
+
# Chat2API โ HuggingFace Space ็๏ผๅๆไปถ้จ็ฝฒ็๏ผ
|
| 3 |
+
# ๅบไบ Node.js๏ผ้ๆ Chat2API ้กน็ฎ + code-server ๆ้ๅฏๅ IDE
|
| 4 |
+
# ๆไน
ๅๅญๅจ๏ผHuggingFace Dataset๏ผๆฏๅฐๆถ่ชๅจๅคไปฝ๏ผ้ๅฏ่ชๅจๆขๅค๏ผ
|
| 5 |
+
#
|
| 6 |
+
# ไฟฎๅค่ฏดๆ๏ผ
|
| 7 |
+
# ๅ็ Dockerfile ็จ RUN cat <<'EOF'> ๅ่ๆฌ๏ผๅ
้จๅตๅฅ heredoc ๆถ
|
| 8 |
+
# BuildKit ไผๆๅๆชๆญ๏ผๅฏผ่ด่ๆฌไธบ็ฉบๆไปถ๏ผ่ฟ่กๆถๆฅ exit 127ใ
|
| 9 |
+
# ๆฌ็ๆน็จ RUN python3 -c "import base64; open(...).write(base64.b64decode(...))"
|
| 10 |
+
# ๅฐๆๆ่ๆฌไปฅ base64 ๅฝขๅผๅ
ๅต๏ผๅฝปๅบ็ปๅผ heredoc ๅตๅฅ้ฎ้ขใ
|
| 11 |
+
# โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 12 |
+
|
| 13 |
+
FROM node:24-slim
|
| 14 |
+
|
| 15 |
+
# โโ 1. ๅบ็ก็ณป็ปไพ่ต + Electron/Chromium ่ฟ่กๅบ + Xvfb โโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 16 |
+
RUN apt-get update && apt-get install -y --no-install-recommends \
|
| 17 |
+
git openssh-client build-essential python3 python3-pip \
|
| 18 |
+
g++ make ca-certificates curl wget nginx \
|
| 19 |
+
xvfb \
|
| 20 |
+
libgbm1 libglib2.0-0 libnss3 libatk1.0-0 libatk-bridge2.0-0 \
|
| 21 |
+
libcups2 libdrm2 libxkbcommon0 libxcomposite1 libxdamage1 \
|
| 22 |
+
libxfixes3 libxrandr2 libpangocairo-1.0-0 libcairo2 \
|
| 23 |
+
libasound2 libxtst6 libx11-xcb1 libxcb-dri3-0 \
|
| 24 |
+
fonts-liberation libappindicator3-1 xdg-utils \
|
| 25 |
+
&& rm -rf /var/lib/apt/lists/*
|
| 26 |
+
|
| 27 |
+
# โโ 1.1. ๅฎ่ฃ
code-server โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 28 |
+
RUN curl -fsSL https://code-server.dev/install.sh | sh
|
| 29 |
+
|
| 30 |
+
# โโ 2. ๅฎ่ฃ
GitHub CLI โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 31 |
+
RUN mkdir -p -m 755 /etc/apt/keyrings \
|
| 32 |
+
&& out=$(mktemp) && wget -nv -O$out https://cli.github.com/packages/githubcli-archive-keyring.gpg \
|
| 33 |
+
&& cat $out | tee /etc/apt/keyrings/githubcli-archive-keyring.gpg > /dev/null \
|
| 34 |
+
&& chmod go+r /etc/apt/keyrings/githubcli-archive-keyring.gpg \
|
| 35 |
+
&& mkdir -p -m 755 /etc/apt/sources.list.d \
|
| 36 |
+
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
|
| 37 |
+
| tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
|
| 38 |
+
&& apt-get update \
|
| 39 |
+
&& apt-get install -y --no-install-recommends gh \
|
| 40 |
+
&& rm -rf /var/lib/apt/lists/* \
|
| 41 |
+
&& gh --version
|
| 42 |
+
|
| 43 |
+
# โโ 3. ๅฎ่ฃ
HuggingFace Hub โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 44 |
+
RUN pip3 install --no-cache-dir huggingface_hub --break-system-packages
|
| 45 |
+
|
| 46 |
+
# โโ 4. ็ฏๅขไธ Git ้
็ฝฎ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 47 |
+
RUN update-ca-certificates && \
|
| 48 |
+
git config --global http.sslVerify false && \
|
| 49 |
+
git config --global url."https://github.com/".insteadOf ssh://git@github.com/
|
| 50 |
+
|
| 51 |
+
# โโ 5. ๅ
้ๅนถๆๅปบ Chat2API ้กน็ฎ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 52 |
+
WORKDIR /app/chat2api
|
| 53 |
+
RUN git clone --depth=1 https://github.com/xiaoY233/Chat2API.git . && \
|
| 54 |
+
npm install && \
|
| 55 |
+
npm run build:linux 2>/dev/null || true
|
| 56 |
+
|
| 57 |
+
# โโ 6. ็ฏๅขๅ้้ป่ฎคๅผ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 58 |
+
ENV PORT=7860 \
|
| 59 |
+
HOME=/root \
|
| 60 |
+
PYTHONUNBUFFERED=1 \
|
| 61 |
+
DISPLAY=:99
|
| 62 |
+
|
| 63 |
+
# โโ 7. ๅ
ๅตๆๆ่ๆฌ๏ผbase64 ็ผ็ ๏ผๅฝปๅบ่ง้ฟ BuildKit heredoc ๅตๅฅๆชๆญ้ฎ้ข๏ผโโโโโโ
|
| 64 |
+
# ๆฏไธช RUN ๆไปค็จ python3 ไธ่กๅฎๆ๏ผdecode โ ๅๆไปถ โ ่ฎพๆ้
|
| 65 |
+
# ๅ็๏ผbase64 ๅญ็ฌฆไธฒไธญไธๅซไปปไฝ shell ็นๆฎๅญ็ฌฆ๏ผไธๅ heredoc ๅฝฑๅใ
|
| 66 |
+
|
| 67 |
+
RUN python3 -c "import base64,os; d=base64.b64decode('aW1wb3J0IG9zLCBzeXMsIHRhcmZpbGUsIHNodXRpbApmcm9tIGh1Z2dpbmdmYWNlX2h1YiBpbXBvcnQgSGZBcGksIGhmX2h1Yl9kb3dubG9hZApmcm9tIGRhdGV0aW1lIGltcG9ydCBkYXRldGltZSwgdGltZWRlbHRhCgphcGkgPSBIZkFwaSgpCnJlcG9faWQgPSBvcy5nZXRlbnYoIkhGX0RBVEFTRVQiKQp0b2tlbiAgID0gb3MuZ2V0ZW52KCJIRl9UT0tFTiIpCgpEQVRBX0RJUiA9ICIvcm9vdC8uY2hhdDJhcGkiCgojIOKUgOKUgCDlt6Xlhbflh73mlbAg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgpkZWYgX3BhcnNlX3NraXBfbGlzdChlbnZfdmFyKToKICAgIHJhdyA9IG9zLmdldGVudihlbnZfdmFyLCAiIikuc3RyaXAoKQogICAgaWYgbm90IHJhdzoKICAgICAgICByZXR1cm4gc2V0KCkKICAgIHJldHVybiB7cy5zdHJpcCgpLnN0cmlwKCIvIikgZm9yIHMgaW4gcmF3LnNwbGl0KCIsIikgaWYgcy5zdHJpcCgpfQoKZGVmIF9pc19za2lwcGVkKHJlbF9wYXRoLCBza2lwX3NldCk6CiAgICByZWwgPSByZWxfcGF0aC5zdHJpcCgiLyIpCiAgICBmb3Igc2tpcCBpbiBza2lwX3NldDoKICAgICAgICBpZiByZWwgPT0gc2tpcCBvciByZWwuc3RhcnRzd2l0aChza2lwICsgIi8iKToKICAgICAgICAgICAgcmV0dXJuIFRydWUKICAgIHJldHVybiBGYWxzZQoKZGVmIF93YWxrX2xvY2FsKGJhc2VfZGlyLCBza2lwX3NldD1Ob25lKToKICAgIHJlc3VsdHMgPSBbXQogICAgaWYgbm90IG9zLnBhdGguaXNkaXIoYmFzZV9kaXIpOgogICAgICAgIHJldHVybiByZXN1bHRzCiAgICBmb3IgZGlycGF0aCwgZGlybmFtZXMsIGZpbGVuYW1lcyBpbiBvcy53YWxrKGJhc2VfZGlyKToKICAgICAgICBmb3IgZm5hbWUgaW4gZmlsZW5hbWVzOgogICAgICAgICAgICBhYnNfcGF0aCA9IG9zLnBhdGguam9pbihkaXJwYXRoLCBmbmFtZSkKICAgICAgICAgICAgcmVsX3RvX2Jhc2UgPSBvcy5wYXRoLnJlbHBhdGgoYWJzX3BhdGgsIGJhc2VfZGlyKQogICAgICAgICAgICBpZiBza2lwX3NldCBpcyBub3QgTm9uZToKICAgICAgICAgICAgICAgIHJlbF90b19kYXRhID0gb3MucGF0aC5yZWxwYXRoKGFic19wYXRoLCBEQVRBX0RJUikKICAgICAgICAgICAgICAgIGlmIF9pc19za2lwcGVkKHJlbF90b19kYXRhLCBza2lwX3NldCk6CiAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgcmVzdWx0cy5hcHBlbmQoKGFic19wYXRoLCByZWxfdG9fYmFzZSkpCiAgICByZXR1cm4gcmVzdWx0cwoKIyDilIDilIAgcmVzdG9yZSgpIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKZGVmIHJlc3RvcmUoKToKICAgIGlmIG5vdCByZXBvX2lkIG9yIG5vdCB0b2tlbjoKICAgICAgICBwcmludCgiU2tpcCBSZXN0b3JlOiBIRl9EQVRBU0VUIG9yIEhGX1RPS0VOIG5vdCBzZXQiKQogICAgICAgIHJldHVybgoKICAgIHJlc3RvcmVfc2tpcF9yYXcgPSBvcy5nZXRlbnYoIlJFU1RPUkVfU0tJUCIsICIiKS5zdHJpcCgpCiAgICBpZiByZXN0b3JlX3NraXBfcmF3ID09ICJhbGwiOgogICAgICAgIHByaW50KCJSZXN0b3JlIHNraXA6IFJFU1RPUkVfU0tJUD1hbGwsIHNraXBwaW5nIGFsbCByZXN0b3JlLiIpCiAgICAgICAgcmV0dXJuCgogICAgSU5JVF9GTEFHID0gImluaXRpYWxpemVkLmZsYWciCiAgICBmb3JjZV9yZXN0b3JlID0gb3MuZ2V0ZW52KCJGT1JDRV9SRVNUT1JFIiwgIiIpLnN0cmlwKCkubG93ZXIoKSBpbiAoInRydWUiLCAiMSIsICJ5ZXMiKQoKICAgIHRyeToKICAgICAgICBhbGxfZmlsZXMgPSBsaXN0KGFwaS5saXN0X3JlcG9fZmlsZXMocmVwb19pZD1yZXBvX2lkLCByZXBvX3R5cGU9ImRhdGFzZXQiLCB0b2tlbj10b2tlbikpCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgcHJpbnQoZiJSZXN0b3JlIEVycm9yIChsaXN0X3JlcG9fZmlsZXMpOiB7ZX0iKQogICAgICAgIHJldHVybgoKICAgIGZsYWdfZXhpc3RzID0gSU5JVF9GTEFHIGluIGFsbF9maWxlcwoKICAgIGlmIG5vdCBmbGFnX2V4aXN0cyBhbmQgbm90IGZvcmNlX3Jlc3RvcmU6CiAgICAgICAgcHJpbnQoIlJlc3RvcmUgc2tpcDogaW5pdGlhbGl6ZWQuZmxhZyBub3QgZm91bmQsIGZpcnN0IGRlcGxveS4iKQogICAgICAgIGltcG9ydCBpbwogICAgICAgIGFwaS51cGxvYWRfZmlsZSgKICAgICAgICAgICAgcGF0aF9vcl9maWxlb2JqPWlvLkJ5dGVzSU8oYiJpbml0aWFsaXplZFxuIiksCiAgICAgICAgICAgIHBhdGhfaW5fcmVwbz1JTklUX0ZMQUcsCiAgICAgICAgICAgIHJlcG9faWQ9cmVwb19pZCwKICAgICAgICAgICAgcmVwb190eXBlPSJkYXRhc2V0IiwKICAgICAgICAgICAgdG9rZW49dG9rZW4sCiAgICAgICAgICAgIGNvbW1pdF9tZXNzYWdlPSJDcmVhdGUgaW5pdGlhbGl6ZWQuZmxhZyBvbiBmaXJzdCBkZXBsb3kiLAogICAgICAgICkKICAgICAgICBwcmludCgiaW5pdGlhbGl6ZWQuZmxhZyBjcmVhdGVkIGluIERhdGFzZXQuIikKICAgICAgICByZXR1cm4KCiAgICBpZiBmb3JjZV9yZXN0b3JlOgogICAgICAgIHByaW50KCJSZXN0b3JlOiBGT1JDRV9SRVNUT1JFPXRydWUsIGlnbm9yaW5nIGluaXRpYWxpemVkLmZsYWcuIikKICAgIGVsc2U6CiAgICAgICAgcHJpbnQoIlJlc3RvcmU6IGluaXRpYWxpemVkLmZsYWcgZm91bmQsIG5vcm1hbCByZXN0YXJ0LiIpCgogICAgc2tpcF9zZXQgPSBfcGFyc2Vfc2tpcF9saXN0KCJSRVNUT1JFX1NLSVAiKQoKICAgIHRyeToKICAgICAgICBub3cgPSBkYXRldGltZS5ub3coKQogICAgICAgIGZvciBpIGluIHJhbmdlKDUpOgogICAgICAgICAgICBkYXkgID0gKG5vdyAtIHRpbWVkZWx0YShkYXlzPWkpKS5zdHJmdGltZSgiJVktJW0tJWQiKQogICAgICAgICAgICBuYW1lID0gZiJiYWNrdXBfe2RheX0udGFyLmd6IgogICAgICAgICAgICBpZiBuYW1lIGluIGFsbF9maWxlczoKICAgICAgICAgICAgICAgIHByaW50KGYiRG93bmxvYWRpbmcge25hbWV9Li4uIikKICAgICAgICAgICAgICAgIHBhdGggPSBoZl9odWJfZG93bmxvYWQocmVwb19pZD1yZXBvX2lkLCBmaWxlbmFtZT1uYW1lLCByZXBvX3R5cGU9ImRhdGFzZXQiLCB0b2tlbj10b2tlbikKICAgICAgICAgICAgICAgIG9zLm1ha2VkaXJzKERBVEFfRElSLCBleGlzdF9vaz1UcnVlKQogICAgICAgICAgICAgICAgd2l0aCB0YXJmaWxlLm9wZW4ocGF0aCwgInI6Z3oiKSBhcyB0YXI6CiAgICAgICAgICAgICAgICAgICAgZm9yIG1lbWJlciBpbiB0YXIuZ2V0bWVtYmVycygpOgogICAgICAgICAgICAgICAgICAgICAgICBpZiBfaXNfc2tpcHBlZChtZW1iZXIubmFtZSwgc2tpcF9zZXQpOgogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJpbnQoZiJSZXN0b3JlIHNraXA6IHttZW1iZXIubmFtZX0iKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgY29udGludWUKICAgICAgICAgICAgICAgICAgICAgICAgdGFyLmV4dHJhY3QobWVtYmVyLCBwYXRoPURBVEFfRElSKQogICAgICAgICAgICAgICAgcHJpbnQoZiJSZXN0b3JlZCBmcm9tIHtuYW1lfSIpCiAgICAgICAgICAgICAgICBicmVhawoKICAgICAgICBpbXBvcnQgaW8KICAgICAgICBhcGkudXBsb2FkX2ZpbGUoCiAgICAgICAgICAgIHBhdGhfb3JfZmlsZW9iaj1pby5CeXRlc0lPKGIiaW5pdGlhbGl6ZWQgYXQgY29udGFpbmVyIHN0YXJ0dXBcbiIpLAogICAgICAgICAgICBwYXRoX2luX3JlcG89SU5JVF9GTEFHLAogICAgICAgICAgICByZXBvX2lkPXJlcG9faWQsCiAgICAgICAgICAgIHJlcG9fdHlwZT0iZGF0YXNldCIsCiAgICAgICAgICAgIHRva2VuPXRva2VuLAogICAgICAgICAgICBjb21taXRfbWVzc2FnZT0iU2V0IGluaXRpYWxpemVkLmZsYWciLAogICAgICAgICkKICAgICAgICBwcmludCgiaW5pdGlhbGl6ZWQuZmxhZyB1cGRhdGVkLiIpCiAgICBleGNlcHQgRXhjZXB0aW9uIGFzIGU6CiAgICAgICAgcHJpbnQoZiJSZXN0b3JlIEVycm9yOiB7ZX0iKQoKCiMg4pSA4pSAIGJhY2t1cCgpIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKZGVmIGJhY2t1cCgpOgogICAgaWYgbm90IHJlcG9faWQgb3Igbm90IHRva2VuOgogICAgICAgIHByaW50KCJTa2lwIEJhY2t1cDogSEZfREFUQVNFVCBvciBIRl9UT0tFTiBub3Qgc2V0IikKICAgICAgICByZXR1cm4KCiAgICBiYWNrdXBfdGFyX3NraXBfcmF3ID0gb3MuZ2V0ZW52KCJCQUNLVVBfVEFSX1NLSVAiLCAiIikuc3RyaXAoKQogICAgaWYgYmFja3VwX3Rhcl9za2lwX3JhdyA9PSAiYWxsIjoKICAgICAgICBwcmludCgiQmFja3VwIHNraXA6IEJBQ0tVUF9UQVJfU0tJUD1hbGwiKQogICAgICAgIHJldHVybgogICAgdHJ5OgogICAgICAgIHRhcl9za2lwX3NldCA9IF9wYXJzZV9za2lwX2xpc3QoIkJBQ0tVUF9UQVJfU0tJUCIpCiAgICAgICAgZGF5ICA9IGRhdGV0aW1lLm5vdygpLnN0cmZ0aW1lKCIlWS0lbS0lZCIpCiAgICAgICAgbmFtZSA9IGYiYmFja3VwX3tkYXl9LnRhci5neiIKICAgICAgICB3aXRoIHRhcmZpbGUub3BlbihuYW1lLCAidzpneiIpIGFzIHRhcjoKICAgICAgICAgICAgZm9yIGFic19wYXRoLCByZWxfdG9fYmFzZSBpbiBfd2Fsa19sb2NhbChEQVRBX0RJUiwgc2tpcF9zZXQ9dGFyX3NraXBfc2V0KToKICAgICAgICAgICAgICAgIHRhci5hZGQoYWJzX3BhdGgsIGFyY25hbWU9cmVsX3RvX2Jhc2UpCiAgICAgICAgYXBpLnVwbG9hZF9maWxlKHBhdGhfb3JfZmlsZW9iaj1uYW1lLCBwYXRoX2luX3JlcG89bmFtZSwgcmVwb19pZD1yZXBvX2lkLCByZXBvX3R5cGU9ImRhdGFzZXQiLCB0b2tlbj10b2tlbikKICAgICAgICBwcmludChmIkJhY2t1cCB7bmFtZX0gZG9uZS4iKQogICAgZXhjZXB0IEV4Y2VwdGlvbiBhcyBlOgogICAgICAgIHByaW50KGYiQmFja3VwIEVycm9yOiB7ZX0iKQoKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICBpZiBsZW4oc3lzLmFyZ3YpID4gMSBhbmQgc3lzLmFyZ3ZbMV0gPT0gImJhY2t1cCI6CiAgICAgICAgYmFja3VwKCkKICAgIGVsc2U6CiAgICAgICAgcmVzdG9yZSgpCg=='); open('/usr/local/bin/sync.py','wb').write(d)"
|
| 68 |
+
|
| 69 |
+
RUN python3 -c "import base64,os,stat; d=base64.b64decode('IyEvdXNyL2Jpbi9lbnYgcHl0aG9uMwoiIiIKY3MtbWFuYWdlcjogY29kZS1zZXJ2ZXIg5oyJ6ZyA5ZCv5YGc5a6I5oqk6L+b56iLCgrnm5HlkKwgVW5peCBzb2NrZXTvvIzmj5DkvpvkuKTkuKogSFRUUCDnq6/ngrnvvJoKICBHRVQgL3dha2V1cCAgICAtIOinpuWPkeWQr+WKqCBjb2RlLXNlcnZlcu+8iOiLpeacqui/kOihjO+8ie+8jOi/lOWbniLlkK/liqjkuK0i562J5b6F6aG1CiAgR0VUIC9oZWFydGJlYXQgLSDmm7TmlrDmnIDlkI7mtLvot4Pml7bpl7TvvIhuZ2lueCDmr4/mrKHmiJDlip/ku6PnkIYgL2lkZS8g5ZCO6LCD55So77yJCgrlkI7lj7Dlrprml7bku7vliqHvvJoKICDmr48gNjAg56eS5qOA5p+l5LiA5qyh77yM6Iul6LaF6L+HIElERV9JRExFX01JTlVURVMg5YiG6ZKf5pegIGhlYXJ0YmVhdO+8jGtpbGwgY29kZS1zZXJ2ZXIKIiIiCgppbXBvcnQgb3MsIHN5cywgdGltZSwgc2lnbmFsLCBzdWJwcm9jZXNzLCB0aHJlYWRpbmcsIHNvY2tldCwgcmUKZnJvbSBodHRwLnNlcnZlciBpbXBvcnQgSFRUUFNlcnZlciwgQmFzZUhUVFBSZXF1ZXN0SGFuZGxlcgoKIyDilIDilIAg6YWN572uIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApTT0NLX1BBVEggICAgICAgID0gIi90bXAvY3MtbWFuYWdlci5zb2NrIgpDU19QT1JUICAgICAgICAgID0gaW50KG9zLmVudmlyb24uZ2V0KCJDT0RFX1NFUlZFUl9QT1JUIiwgIjEzMzM3IikpCkNTX1BBU1NXT1JEICAgICAgPSBvcy5lbnZpcm9uLmdldCgiQ09ERV9TRVJWRVJfUEFTU1dPUkQiLCAiY2hhbmdlbWUxMjMhIikKSURMRV9NSU5VVEVTICAgICA9IGludChvcy5lbnZpcm9uLmdldCgiSURFX0lETEVfTUlOVVRFUyIsICIzMCIpKQpDSEVDS19JTlRFUlZBTCAgID0gNjAgICAjIOenkgoKIyDilIDilIAg5YWo5bGA54q25oCBIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgApjc19wcm9jICAgICAgICAgID0gTm9uZQpsYXN0X2hlYXJ0YmVhdCAgID0gMC4wCmxvY2sgICAgICAgICAgICAgPSB0aHJlYWRpbmcuTG9jaygpCgojIOKUgOKUgCBjb2RlLXNlcnZlciDlkK/lgZwg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACgpkZWYgc3RhcnRfY3MoKToKICAgIGdsb2JhbCBjc19wcm9jLCBsYXN0X2hlYXJ0YmVhdAogICAgd2l0aCBsb2NrOgogICAgICAgIGlmIGNzX3Byb2MgaXMgbm90IE5vbmUgYW5kIGNzX3Byb2MucG9sbCgpIGlzIE5vbmU6CiAgICAgICAgICAgIHJldHVybiAgIyBhbHJlYWR5IHJ1bm5pbmcKICAgICAgICBwcmludChmIltjcy1tYW5hZ2VyXSBTdGFydGluZyBjb2RlLXNlcnZlciBvbiBwb3J0IHtDU19QT1JUfSIsIGZsdXNoPVRydWUpCiAgICAgICAgZW52ID0gb3MuZW52aXJvbi5jb3B5KCkKICAgICAgICBlbnZbIlBBU1NXT1JEIl0gPSBDU19QQVNTV09SRAogICAgICAgIGNzX3Byb2MgPSBzdWJwcm9jZXNzLlBvcGVuKAogICAgICAgICAgICBbImNvZGUtc2VydmVyIiwKICAgICAgICAgICAgICItLWJpbmQtYWRkciIsIGYiMTI3LjAuMC4xOntDU19QT1JUfSIsCiAgICAgICAgICAgICAiLS1hdXRoIiwgInBhc3N3b3JkIiwKICAgICAgICAgICAgICItLWRpc2FibGUtdGVsZW1ldHJ5IiwKICAgICAgICAgICAgICIvYXBwL2NoYXQyYXBpIl0sCiAgICAgICAgICAgIGVudj1lbnYsCiAgICAgICAgICAgIHN0ZG91dD1zdWJwcm9jZXNzLkRFVk5VTEwsCiAgICAgICAgICAgIHN0ZGVycj1zdWJwcm9jZXNzLkRFVk5VTEwsCiAgICAgICAgKQogICAgICAgIGxhc3RfaGVhcnRiZWF0ID0gdGltZS50aW1lKCkKICAgICAgICBwcmludChmIltjcy1tYW5hZ2VyXSBjb2RlLXNlcnZlciBwaWQ9e2NzX3Byb2MucGlkfSIsIGZsdXNoPVRydWUpCgpkZWYgc3RvcF9jcygpOgogICAgZ2xvYmFsIGNzX3Byb2MKICAgIHdpdGggbG9jazoKICAgICAgICBpZiBjc19wcm9jIGlzIE5vbmUgb3IgY3NfcHJvYy5wb2xsKCkgaXMgbm90IE5vbmU6CiAgICAgICAgICAgIHJldHVybgogICAgICAgIHByaW50KCJbY3MtbWFuYWdlcl0gU3RvcHBpbmcgaWRsZSBjb2RlLXNlcnZlciIsIGZsdXNoPVRydWUpCiAgICAgICAgY3NfcHJvYy50ZXJtaW5hdGUoKQogICAgICAgIHRyeToKICAgICAgICAgICAgY3NfcHJvYy53YWl0KHRpbWVvdXQ9MTApCiAgICAgICAgZXhjZXB0IHN1YnByb2Nlc3MuVGltZW91dEV4cGlyZWQ6CiAgICAgICAgICAgIGNzX3Byb2Mua2lsbCgpCiAgICAgICAgY3NfcHJvYyA9IE5vbmUKCmRlZiBpc19jc19ydW5uaW5nKCk6CiAgICB3aXRoIGxvY2s6CiAgICAgICAgcmV0dXJuIGNzX3Byb2MgaXMgbm90IE5vbmUgYW5kIGNzX3Byb2MucG9sbCgpIGlzIE5vbmUKCiMg4pSA4pSAIEhUVFAgaGFuZGxlciDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCldBS0VVUF9IVE1MID0gIiIiXAo8IURPQ1RZUEUgaHRtbD48aHRtbD48aGVhZD4KPG1ldGEgY2hhcnNldD0idXRmLTgiPgo8bWV0YSBodHRwLWVxdWl2PSJyZWZyZXNoIiBjb250ZW50PSIzIj4KPHRpdGxlPklERSDlkK/liqjkuK3igKY8L3RpdGxlPgo8c3R5bGU+CmJvZHl7Zm9udC1mYW1pbHk6c2Fucy1zZXJpZjtkaXNwbGF5OmZsZXg7YWxpZ24taXRlbXM6Y2VudGVyO2p1c3RpZnktY29udGVudDpjZW50ZXI7CiAgICAgaGVpZ2h0OjEwMHZoO21hcmdpbjowO2JhY2tncm91bmQ6IzFlMWUyZTtjb2xvcjojY2RkNmY0fQouYm94e3RleHQtYWxpZ246Y2VudGVyfS5zcGlubmVye3dpZHRoOjQ4cHg7aGVpZ2h0OjQ4cHg7Ym9yZGVyOjVweCBzb2xpZCAjMzEzMjQ0OwogICAgIGJvcmRlci10b3AtY29sb3I6Izg5YjRmYTtib3JkZXItcmFkaXVzOjUwJTthbmltYXRpb246c3BpbiAxcyBsaW5lYXIgaW5maW5pdGU7CiAgICAgbWFyZ2luOjAgYXV0byAyMHB4fQpAa2V5ZnJhbWVzIHNwaW57dG97dHJhbnNmb3JtOnJvdGF0ZSgzNjBkZWcpfX0KPC9zdHlsZT48L2hlYWQ+PGJvZHk+CjxkaXYgY2xhc3M9ImJveCI+CiAgPGRpdiBjbGFzcz0ic3Bpbm5lciI+PC9kaXY+CiAgPGgyPlZTIENvZGUgSURFIOWQr+WKqOS4reKApjwvaDI+CiAgPHA+6aG16Z2i5bCG5ZyoIDMg56eS5ZCO6Ieq5Yqo5Yi35pawPC9wPgo8L2Rpdj48L2JvZHk+PC9odG1sPgoiIiIKCmNsYXNzIEhhbmRsZXIoQmFzZUhUVFBSZXF1ZXN0SGFuZGxlcik6CiAgICBkZWYgbG9nX21lc3NhZ2Uoc2VsZiwgZm10LCAqYXJncyk6CiAgICAgICAgcGFzcyAgIyDpnZnpu5jorr/pl67ml6Xlv5cKCiAgICBkZWYgZG9fR0VUKHNlbGYpOgogICAgICAgIGdsb2JhbCBsYXN0X2hlYXJ0YmVhdAogICAgICAgIHBhdGggPSBzZWxmLnBhdGguc3BsaXQoIj8iKVswXQoKICAgICAgICBpZiBwYXRoID09ICIvaGVhcnRiZWF0IjoKICAgICAgICAgICAgbGFzdF9oZWFydGJlYXQgPSB0aW1lLnRpbWUoKQogICAgICAgICAgICBzZWxmLl9yZXNwb25kKDIwMCwgInRleHQvcGxhaW4iLCBiIm9rIikKCiAgICAgICAgZWxpZiBwYXRoID09ICIvd2FrZXVwIjoKICAgICAgICAgICAgaWYgbm90IGlzX2NzX3J1bm5pbmcoKToKICAgICAgICAgICAgICAgIHN0YXJ0X2NzKCkKICAgICAgICAgICAgICAgICMg562J5b6FIGNvZGUtc2VydmVyIOWwsee7qu+8iOacgOWkmiAzMCDnp5LvvIkKICAgICAgICAgICAgICAgIGZvciBfIGluIHJhbmdlKDMwKToKICAgICAgICAgICAgICAgICAgICB0aW1lLnNsZWVwKDEpCiAgICAgICAgICAgICAgICAgICAgdHJ5OgogICAgICAgICAgICAgICAgICAgICAgICBpbXBvcnQgdXJsbGliLnJlcXVlc3QKICAgICAgICAgICAgICAgICAgICAgICAgdXJsbGliLnJlcXVlc3QudXJsb3BlbihmImh0dHA6Ly8xMjcuMC4wLjE6e0NTX1BPUlR9LyIsIHRpbWVvdXQ9MSkKICAgICAgICAgICAgICAgICAgICAgICAgYnJlYWsKICAgICAgICAgICAgICAgICAgICBleGNlcHQgRXhjZXB0aW9uOgogICAgICAgICAgICAgICAgICAgICAgICBwYXNzCiAgICAgICAgICAgIHNlbGYuX3Jlc3BvbmQoMjAwLCAidGV4dC9odG1sIiwgV0FLRVVQX0hUTUwuZW5jb2RlKCkpCgogICAgICAgIGVsc2U6CiAgICAgICAgICAgIHNlbGYuX3Jlc3BvbmQoNDA0LCAidGV4dC9wbGFpbiIsIGIibm90IGZvdW5kIikKCiAgICBkZWYgX3Jlc3BvbmQoc2VsZiwgY29kZSwgY3R5cGUsIGJvZHkpOgogICAgICAgIHNlbGYuc2VuZF9yZXNwb25zZShjb2RlKQogICAgICAgIHNlbGYuc2VuZF9oZWFkZXIoIkNvbnRlbnQtVHlwZSIsIGN0eXBlKQogICAgICAgIHNlbGYuc2VuZF9oZWFkZXIoIkNvbnRlbnQtTGVuZ3RoIiwgc3RyKGxlbihib2R5KSkpCiAgICAgICAgc2VsZi5lbmRfaGVhZGVycygpCiAgICAgICAgc2VsZi53ZmlsZS53cml0ZShib2R5KQoKIyDilIDilIAgVW5peCBzb2NrZXQgc2VydmVyIOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAoKY2xhc3MgVW5peEhUVFBTZXJ2ZXIoSFRUUFNlcnZlcik6CiAgICBhZGRyZXNzX2ZhbWlseSA9IHNvY2tldC5BRl9VTklYCgogICAgZGVmIHNlcnZlcl9iaW5kKHNlbGYpOgogICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKFNPQ0tfUEFUSCk6CiAgICAgICAgICAgIG9zLnJlbW92ZShTT0NLX1BBVEgpCiAgICAgICAgc3VwZXIoKS5zZXJ2ZXJfYmluZCgpCiAgICAgICAgb3MuY2htb2QoU09DS19QQVRILCAwbzY2NikKCiMg4pSA4pSAIOepuumXsuajgOafpeW+queOryDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCmRlZiBpZGxlX3dhdGNoZXIoKToKICAgIHdoaWxlIFRydWU6CiAgICAgICAgdGltZS5zbGVlcChDSEVDS19JTlRFUlZBTCkKICAgICAgICBpZiBpc19jc19ydW5uaW5nKCk6CiAgICAgICAgICAgIGlkbGVfc2VjcyA9IHRpbWUudGltZSgpIC0gbGFzdF9oZWFydGJlYXQKICAgICAgICAgICAgaWYgaWRsZV9zZWNzID4gSURMRV9NSU5VVEVTICogNjA6CiAgICAgICAgICAgICAgICBwcmludChmIltjcy1tYW5hZ2VyXSBJZGxlIHtpZGxlX3NlY3MvNjA6LjFmfSBtaW4gPiB7SURMRV9NSU5VVEVTfSBtaW4sIHN0b3BwaW5nLiIsIGZsdXNoPVRydWUpCiAgICAgICAgICAgICAgICBzdG9wX2NzKCkKCiMg4pSA4pSAIOS4u+WFpeWPoyDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKCmlmIF9fbmFtZV9fID09ICJfX21haW5fXyI6CiAgICB3YXRjaGVyID0gdGhyZWFkaW5nLlRocmVhZCh0YXJnZXQ9aWRsZV93YXRjaGVyLCBkYWVtb249VHJ1ZSkKICAgIHdhdGNoZXIuc3RhcnQoKQoKICAgIHNlcnZlciA9IFVuaXhIVFRQU2VydmVyKFNPQ0tfUEFUSCwgSGFuZGxlcikKICAgIHByaW50KGYiW2NzLW1hbmFnZXJdIExpc3RlbmluZyBvbiB7U09DS19QQVRIfSIsIGZsdXNoPVRydWUpCiAgICB0cnk6CiAgICAgICAgc2VydmVyLnNlcnZlX2ZvcmV2ZXIoKQogICAgZXhjZXB0IEtleWJvYXJkSW50ZXJydXB0OgogICAgICAgIHBhc3MK'); p='/usr/local/bin/cs-manager'; open(p,'wb').write(d); os.chmod(p, os.stat(p).st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)"
|
| 70 |
+
|
| 71 |
+
RUN python3 -c "import base64,os,stat; d=base64.b64decode('IyEvYmluL2Jhc2gKc2V0IC1lCgojIOKUgOKUgCDnjq/looPlj5jph48g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACkxJU1RFTl9QT1JUPSIke1BPUlQ6LTc4NjB9IgpDSEFUMkFQSV9QT1JUPTc4NjIKQ09ERV9TRVJWRVJfUE9SVD0iJHtDT0RFX1NFUlZFUl9QT1JUOi0xMzMzN30iCklERV9JRExFX01JTlVURVM9IiR7SURFX0lETEVfTUlOVVRFUzotMzB9IgoKZXhwb3J0IENPREVfU0VSVkVSX1BPUlQgSURFX0lETEVfTUlOVVRFUwpleHBvcnQgQ09ERV9TRVJWRVJfUEFTU1dPUkQ9IiR7Q09ERV9TRVJWRVJfUEFTU1dPUkQ6LWNoYW5nZW1lMTIzIX0iCmV4cG9ydCBDSEFUMkFQSV9EQVRBX0RJUj0iL3Jvb3QvLmNoYXQyYXBpIgoKZWNobyAiPT09IENoYXQyQVBJIEh1Z2dpbmdGYWNlIFNwYWNlIOWQr+WKqCA9PT0iCmVjaG8gIuWklumDqOebkeWQrOerr+WPoyAgICA6ICR7TElTVEVOX1BPUlR9IgplY2hvICJDaGF0MkFQSeWGhemDqOerr+WPozogJHtDSEFUMkFQSV9QT1JUfSIKZWNobyAiSURF5YaF6YOo56uv5Y+jICAgICA6ICR7Q09ERV9TRVJWRVJfUE9SVH0iCmVjaG8gIklERemXsue9ruiHquWKqOWFs+mXrSA6ICR7SURFX0lETEVfTUlOVVRFU30gbWluIgoKIyDilIDilIAg5q2l6aqkIDHvvJrku44gSEYgRGF0YXNldCDmgaLlpI3mjIHkuYXljJbmlbDmja4g4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACmVjaG8gIi0tLSDmgaLlpI3mjIHkuYXljJbphY3nva7mlbDmja4gLS0tIgpweXRob24zIC91c3IvbG9jYWwvYmluL3N5bmMucHkgcmVzdG9yZSB8fCB0cnVlCgojIOKUgOKUgCDmraXpqqQgMu+8muWHhuWkhyBDaGF0MkFQSSDmlbDmja7nm67lvZUg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACm1rZGlyIC1wICIke0NIQVQyQVBJX0RBVEFfRElSfS9sb2dzIiAiJHtDSEFUMkFQSV9EQVRBX0RJUn0vc2Vzc2lvbnMiCgojIOiLpeaVsOaNruebruW9leS4jeWtmOWcqOmFjee9ru+8jOS7jiBIRiBTcGFjZSDlj5jph4/nlJ/miJDliJ3lp4vphY3nva4KQ09ORklHX0ZJTEU9IiR7Q0hBVDJBUElfREFUQV9ESVJ9L2NvbmZpZy5qc29uIgppZiBbICEgLWYgIiRDT05GSUdfRklMRSIgXTsgdGhlbgogICAgZWNobyAi55Sf5oiQ5Yid5aeLIGNvbmZpZy5qc29uLi4uIgogICAgcHl0aG9uMyAvdXNyL2xvY2FsL2Jpbi9pbml0LWNvbmZpZy5weQpmaQoKIyDoi6XmlbDmja7nm67lvZXkuI3lrZjlnKjotKbmiLfliJfooajvvIzku44gSEYgU3BhY2Ug5Y+Y6YeP5rOo5YWl5Yid5aeL6LSm5oi3CkFDQ09VTlRTX0ZJTEU9IiR7Q0hBVDJBUElfREFUQV9ESVJ9L2FjY291bnRzLmpzb24iCmlmIFsgISAtZiAiJEFDQ09VTlRTX0ZJTEUiIF07IHRoZW4KICAgIGVjaG8gIueUn+aIkOWIneWniyBhY2NvdW50cy5qc29uLi4uIgogICAgcHl0aG9uMyAvdXNyL2xvY2FsL2Jpbi9pbml0LWFjY291bnRzLnB5CmZpCgojIOKUgOKUgCDmraXpqqQgM++8muWQr+WKqOiZmuaLn+aYvuekuu+8iEVsZWN0cm9uIOmcgOimgSBYIGRpc3BsYXnvvInilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKZWNobyAiLS0tIOWQr+WKqCBYdmZiIOiZmuaLn+aYvuekuiAtLS0iClh2ZmIgOjk5IC1zY3JlZW4gMCAxMjgweDgwMHgyNCAtYWMgK2V4dGVuc2lvbiBHTFggK3JlbmRlciAtbm9yZXNldCAmClhWRkJfUElEPSQhCmV4cG9ydCBESVNQTEFZPTo5OQplY2hvICLnrYnlvoUgWHZmYiDlsLHnu6ouLi4iCnNsZWVwIDIKCiMg4pSA4pSAIOatpemqpCA077ya5ZCv5YqoIENoYXQyQVBJ77yIRWxlY3Ryb24gQXBwSW1hZ2XvvIzlkI7lj7Dov5DooYzvvInilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKZWNobyAiLS0tIOWQr+WKqCBDaGF0MkFQSSDmnI3liqEgKOWGhemDqOerr+WPoyAke0NIQVQyQVBJX1BPUlR9KSAtLS0iCmNkIC9hcHAvY2hhdDJhcGkKCiMg5p+l5om+5p6E5bu65Lqn54mp77ya5LyY5YWI55SoIEFwcEltYWdl77yM5YW25qyh55SoIHVucGFja2VkIOebruW9leWGheeahCBlbGVjdHJvbiDlj6/miafooYzmlofku7YKQVBQSU1BR0U9JChmaW5kIGRpc3QgLW5hbWUgIiouQXBwSW1hZ2UiIC1tYXhkZXB0aCAxIDI+L2Rldi9udWxsIHwgaGVhZCAtMSkKCmlmIFsgLW4gIiRBUFBJTUFHRSIgXTsgdGhlbgogICAgZWNobyAi5L2/55SoIEFwcEltYWdlOiAkQVBQSU1BR0UiCiAgICBDSEFUMkFQSV9QT1JUPSIke0NIQVQyQVBJX1BPUlR9IiBcCiAgICBDSEFUMkFQSV9EQVRBPSIke0NIQVQyQVBJX0RBVEFfRElSfSIgXAogICAgIiRBUFBJTUFHRSIgLS1uby1zYW5kYm94IC0tZGlzYWJsZS1ncHUgMj4mMSBcCiAgICAgICAgfCB0ZWUgIiR7Q0hBVDJBUElfREFUQV9ESVJ9L2xvZ3MvY2hhdDJhcGkubG9nIiAmCmVsc2UKICAgICMg5Zue6YCA77ya5L2/55SoIGVsZWN0cm9uLXZpdGUgZGV2IOaooeW8j++8iOS7jemcgCBESVNQTEFZ77yJCiAgICBlY2hvICJBcHBJbWFnZSDmnKrmib7liLDvvIzlm57pgIDliLAgZWxlY3Ryb24tdml0ZSBkZXYg5qih5byPIgogICAgQ0hBVDJBUElfUE9SVD0iJHtDSEFUMkFQSV9QT1JUfSIgXAogICAgQ0hBVDJBUElfREFUQT0iJHtDSEFUMkFQSV9EQVRBX0RJUn0iIFwKICAgIG5weCBlbGVjdHJvbiAuIC0tbm8tc2FuZGJveCAtLWRpc2FibGUtZ3B1IDI+JjEgXAogICAgICAgIHwgdGVlICIke0NIQVQyQVBJX0RBVEFfRElSfS9sb2dzL2NoYXQyYXBpLmxvZyIgJgpmaQplY2hvICJDaGF0MkFQSSDlt7LlnKjlkI7lj7DlkK/liqgiCgojIOKUgOKUgCDmraXpqqQgNe+8muWQr+WKqCBjcy1tYW5hZ2VyIOWuiOaKpOi/m+eoi++8iOWQjuWPsO+8ieKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgOKUgAplY2hvICItLS0g5ZCv5YqoIElERSDmjInpnIDnrqHnkIblmaggLS0tIgpweXRob24zIC91c3IvbG9jYWwvYmluL2NzLW1hbmFnZXIgMj4mMSB8IHRlZSAiJHtDSEFUMkFQSV9EQVRBX0RJUn0vbG9ncy9jcy1tYW5hZ2VyLmxvZyIgJgoKIyDnrYnlvoUgY3MtbWFuYWdlciBzb2NrZXQg5bCx57uq77yI5pyA5aSaIDEwIOenku+8iQpmb3IgaSBpbiAkKHNlcSAxIDIwKTsgZG8KICBpZiBbIC1TIC90bXAvY3MtbWFuYWdlci5zb2NrIF07IHRoZW4KICAgIGVjaG8gImNzLW1hbmFnZXIgc29ja2V0IOWwsee7qiIKICAgIGJyZWFrCiAgZmkKICBzbGVlcCAwLjUKZG9uZQoKIyDilIDilIAg5q2l6aqkIDbvvJrnrYnlvoUgQ2hhdDJBUEkg5pyN5Yqh5bCx57uq77yI5pyA5aSaIDEyMCDnp5LvvInilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKZWNobyAi562J5b6FIENoYXQyQVBJIOacjeWKoeWQr+WKqC4uLiIKZm9yIGkgaW4gJChzZXEgMSA2MCk7IGRvCiAgaWYgY3VybCAtZnNTICJodHRwOi8vMTI3LjAuMC4xOiR7Q0hBVDJBUElfUE9SVH0vIiA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICAgIGVjaG8gIkNoYXQyQVBJIOacjeWKoeW3suWwsee7qu+8iCR7aX0qMnPvvIkiCiAgICBicmVhawogIGZpCiAgc2xlZXAgMgpkb25lCgojIOKUgOKUgCDmraXpqqQgN++8muWQr+WKqOWumuaXtuWkh+S7veW+queOr++8iOavjyA2MCDliIbpkp/vvInilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIDilIAKKHdoaWxlIHRydWU7IGRvIHNsZWVwIDM2MDA7IHB5dGhvbjMgL3Vzci9sb2NhbC9iaW4vc3luYy5weSBiYWNrdXAgfHwgdHJ1ZTsgZG9uZSkgJgplY2hvICLmjIHkuYXljJblpIfku73lvqrnjq/lt7LlkK/liqjvvIjmr4/lsI/ml7blpIfku73kuIDmrKHvvIkiCgojIOKUgOKUgCDmraXpqqQgOO+8mueUn+aIkCBuZ2lueCDphY3nva7lubblkK/liqgg4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSA4pSACnJtIC1mIC9ldGMvbmdpbngvc2l0ZXMtZW5hYmxlZC9kZWZhdWx0IC9ldGMvbmdpbngvY29uZi5kL2RlZmF1bHQuY29uZgoKTkdJTlhfQ09ORj0iL2V0Yy9uZ2lueC9jb25mLmQvY2hhdDJhcGkuY29uZiIKCnB5dGhvbjMgLSA8PFBZRU9GCmltcG9ydCBvcwpsaXN0ZW4gICA9IG9zLmVudmlyb24uZ2V0KCJQT1JUIiwgIjc4NjAiKQpjc19wb3J0ICA9IG9zLmVudmlyb24uZ2V0KCJDT0RFX1NFUlZFUl9QT1JUIiwgIjEzMzM3IikKYXBpX3BvcnQgPSAiNzg2MiIKCmNvbmYgPSBmIiIiCnVwc3RyZWFtIGNzX21hbmFnZXIge3sKICAgIHNlcnZlciB1bml4Oi90bXAvY3MtbWFuYWdlci5zb2NrOwp9fQoKc2VydmVyIHt7CiAgICBsaXN0ZW4ge2xpc3Rlbn07CiAgICBzZXJ2ZXJfbmFtZSBfOwogICAgY2xpZW50X21heF9ib2R5X3NpemUgMTAwTTsKCiAgICBhY2Nlc3NfbG9nIC9kZXYvc3Rkb3V0OwogICAgZXJyb3JfbG9nICAvZGV2L3N0ZGVyciB3YXJuOwoKICAgIGFic29sdXRlX3JlZGlyZWN0IG9mZjsKICAgIHBvcnRfaW5fcmVkaXJlY3Qgb2ZmOwoKICAgIGxvY2F0aW9uIC9pZGUvIHt7CiAgICAgICAgcHJveHlfcGFzcyBodHRwOi8vMTI3LjAuMC4xOntjc19wb3J0fS87CiAgICAgICAgcHJveHlfaHR0cF92ZXJzaW9uIDEuMTsKICAgICAgICBwcm94eV9zZXRfaGVhZGVyIEhvc3QgXCRob3N0OwogICAgICAgIHByb3h5X3NldF9oZWFkZXIgVXBncmFkZSBcJGh0dHBfdXBncmFkZTsKICAgICAgICBwcm94eV9zZXRfaGVhZGVyIENvbm5lY3Rpb24gInVwZ3JhZGUiOwogICAgICAgIHByb3h5X3NldF9oZWFkZXIgWC1Gb3J3YXJkZWQtRm9yIFwkcHJveHlfYWRkX3hfZm9yd2FyZGVkX2ZvcjsKICAgICAgICBwcm94eV9zZXRfaGVhZGVyIFgtRm9yd2FyZGVkLVByb3RvIFwkc2NoZW1lOwogICAgICAgIHByb3h5X3JlZGlyZWN0IC8gL2lkZS87CiAgICAgICAgcHJveHlfcmVhZF90aW1lb3V0IDg2NDAwOwogICAgICAgIHByb3h5X2Nvbm5lY3RfdGltZW91dCAyczsKICAgICAgICBlcnJvcl9wYWdlIDUwMiA1MDQgQGlkZV93YWtldXA7CiAgICAgICAgcG9zdF9hY3Rpb24gL2lkZS1oZWFydGJlYXQvOwogICAgfX0KCiAgICBsb2NhdGlvbiAvaWRlLWhlYXJ0YmVhdC8ge3sKICAgICAgICBpbnRlcm5hbDsKICAgICAgICByZXdyaXRlIF4gL2hlYXJ0YmVhdCBicmVhazsKICAgICAgICBwcm94eV9wYXNzIGh0dHA6Ly9jc19tYW5hZ2VyOwogICAgICAgIHByb3h5X2Nvbm5lY3RfdGltZW91dCAxczsKICAgICAgICBwcm94eV9yZWFkX3RpbWVvdXQgMnM7CiAgICB9fQoKICAgIGxvY2F0aW9uIEBpZGVfd2FrZXVwIHt7CiAgICAgICAgcmV3cml0ZSBeIC93YWtldXAgYnJlYWs7CiAgICAgICAgcHJveHlfcGFzcyBodHRwOi8vY3NfbWFuYWdlcjsKICAgICAgICBwcm94eV9odHRwX3ZlcnNpb24gMS4xOwogICAgICAgIHByb3h5X3NldF9oZWFkZXIgSG9zdCBcJGhvc3Q7CiAgICAgICAgcHJveHlfY29ubmVjdF90aW1lb3V0IDVzOwogICAgICAgIHByb3h5X3JlYWRfdGltZW91dCAzMHM7CiAgICB9fQoKICAgIGxvY2F0aW9uIC8ge3sKICAgICAgICBwcm94eV9wYXNzIGh0dHA6Ly8xMjcuMC4wLjE6e2FwaV9wb3J0fS87CiAgICAgICAgcHJveHlfaHR0cF92ZXJzaW9uIDEuMTsKICAgICAgICBwcm94eV9zZXRfaGVhZGVyIEhvc3QgXCRob3N0OwogICAgICAgIHByb3h5X3NldF9oZWFkZXIgVXBncmFkZSBcJGh0dHBfdXBncmFkZTsKICAgICAgICBwcm94eV9zZXRfaGVhZGVyIENvbm5lY3Rpb24gInVwZ3JhZGUiOwogICAgICAgIHByb3h5X3NldF9oZWFkZXIgWC1Gb3J3YXJkZWQtRm9yIFwkcHJveHlfYWRkX3hfZm9yd2FyZGVkX2ZvcjsKICAgICAgICBwcm94eV9zZXRfaGVhZGVyIFgtRm9yd2FyZGVkLVByb3RvIFwkc2NoZW1lOwogICAgICAgIHByb3h5X3JlYWRfdGltZW91dCA4NjQwMDsKICAgIH19Cn19CiIiIgoKd2l0aCBvcGVuKCIvZXRjL25naW54L2NvbmYuZC9jaGF0MmFwaS5jb25mIiwgInciKSBhcyBmOgogICAgZi53cml0ZShjb25mKQpwcmludCgibmdpbnggY29uZmlnIHdyaXR0ZW4uIikKUFlFT0YKCmVjaG8gIm5naW54IOmFjee9ruW3sueUn+aIkCIKbmdpbnggLXQKZWNobyAi5ZCv5YqoIG5naW5477yI5YmN5Y+w6L+Q6KGM77yM5L+d5oyB5a655Zmo5a2Y5rS777yJLi4uIgpleGVjIG5naW54IC1nICdkYWVtb24gb2ZmOyBlcnJvcl9sb2cgL2Rldi9zdGRlcnIgd2FybjsnCg=='); p='/usr/local/bin/start-chat2api'; open(p,'wb').write(d); os.chmod(p, os.stat(p).st_mode | stat.S_IEXEC | stat.S_IXGRP | stat.S_IXOTH)"
|
| 72 |
+
|
| 73 |
+
RUN python3 -c "import base64,os; d=base64.b64decode('aW1wb3J0IG9zLCBqc29uCgpjb25maWcgPSB7CiAgICAicG9ydCI6IGludChvcy5nZXRlbnYoIkNIQVQyQVBJX1BPUlQiLCAiNzg2MiIpKSwKICAgICJhcGlLZXkiOiBvcy5nZXRlbnYoIkNIQVQyQVBJX0FQSV9LRVkiLCAiIiksCiAgICAibG9hZEJhbGFuY2UiOiB7CiAgICAgICAgInN0cmF0ZWd5Ijogb3MuZ2V0ZW52KCJMQl9TVFJBVEVHWSIsICJyb3VuZC1yb2JpbiIpCiAgICB9Cn0KZGF0YV9kaXIgPSBvcy5nZXRlbnYoIkNIQVQyQVBJX0RBVEFfRElSIiwgIi9yb290Ly5jaGF0MmFwaSIpCm9zLm1ha2VkaXJzKGRhdGFfZGlyLCBleGlzdF9vaz1UcnVlKQp3aXRoIG9wZW4ob3MucGF0aC5qb2luKGRhdGFfZGlyLCAiY29uZmlnLmpzb24iKSwgInciKSBhcyBmOgogICAganNvbi5kdW1wKGNvbmZpZywgZiwgaW5kZW50PTIpCnByaW50KCJjb25maWcuanNvbiBpbml0aWFsaXplZC4iKQo='); open('/usr/local/bin/init-config.py','wb').write(d)"
|
| 74 |
+
|
| 75 |
+
RUN python3 -c "import base64,os; d=base64.b64decode('aW1wb3J0IG9zLCBqc29uCgphY2NvdW50cyA9IFtdCmZvciBpIGluIHJhbmdlKDEsIDExKToKICAgIHB0eXBlID0gb3MuZ2V0ZW52KGYiUFJPVklERVJfe2l9X1RZUEUiLCAiIikuc3RyaXAoKQogICAgdG9rZW4gPSBvcy5nZXRlbnYoZiJQUk9WSURFUl97aX1fVE9LRU4iLCAiIikuc3RyaXAoKQogICAgaWYgcHR5cGUgYW5kIHRva2VuOgogICAgICAgIGFjY291bnRzLmFwcGVuZCh7InByb3ZpZGVyIjogcHR5cGUsICJ0b2tlbiI6IHRva2VuLCAiZW5hYmxlZCI6IFRydWV9KQoKZGF0YV9kaXIgPSBvcy5nZXRlbnYoIkNIQVQyQVBJX0RBVEFfRElSIiwgIi9yb290Ly5jaGF0MmFwaSIpCm9zLm1ha2VkaXJzKGRhdGFfZGlyLCBleGlzdF9vaz1UcnVlKQp3aXRoIG9wZW4ob3MucGF0aC5qb2luKGRhdGFfZGlyLCAiYWNjb3VudHMuanNvbiIpLCAidyIpIGFzIGY6CiAgICBqc29uLmR1bXAoYWNjb3VudHMsIGYsIGluZGVudD0yKQpwcmludChmImFjY291bnRzLmpzb24gaW5pdGlhbGl6ZWQgd2l0aCB7bGVuKGFjY291bnRzKX0gYWNjb3VudChzKS4iKQo='); open('/usr/local/bin/init-accounts.py','wb').write(d)"
|
| 76 |
+
|
| 77 |
+
# โโ 8. ้ช่ฏ่ๆฌๅๅ
ฅๅฎๆด๏ผๆๅปบๆถๆ ก้ช๏ผๆ้ฎ้ข็ซๅณๆฅ้๏ผโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 78 |
+
RUN python3 -c "
|
| 79 |
+
import os, stat
|
| 80 |
+
scripts = {
|
| 81 |
+
'/usr/local/bin/sync.py': False,
|
| 82 |
+
'/usr/local/bin/cs-manager': True,
|
| 83 |
+
'/usr/local/bin/start-chat2api': True,
|
| 84 |
+
'/usr/local/bin/init-config.py': False,
|
| 85 |
+
'/usr/local/bin/init-accounts.py': False,
|
| 86 |
+
}
|
| 87 |
+
for path, need_exec in scripts.items():
|
| 88 |
+
assert os.path.exists(path), f'MISSING: {path}'
|
| 89 |
+
size = os.path.getsize(path)
|
| 90 |
+
assert size > 100, f'TOO SMALL ({size}B): {path}'
|
| 91 |
+
if need_exec:
|
| 92 |
+
mode = os.stat(path).st_mode
|
| 93 |
+
assert mode & stat.S_IEXEC, f'NOT EXECUTABLE: {path}'
|
| 94 |
+
print(f'OK {size:6d}B {path}')
|
| 95 |
+
print('All scripts verified.')
|
| 96 |
+
"
|
| 97 |
+
|
| 98 |
+
# โโ 9. ๆด้ฒ็ซฏๅฃ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 99 |
+
EXPOSE 7860
|
| 100 |
+
|
| 101 |
+
# โโ ่ฎฟ้ฎ่ฏดๆ โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
|
| 102 |
+
# Space ๅฏๅจๅ่ฎฟ้ฎๆนๅผ๏ผ
|
| 103 |
+
# ไธป็้ข (Chat2API ็ฎก็้ขๆฟ) : https://<your-space>.hf.space/
|
| 104 |
+
# API ๆฅๅ
ฅ็ซฏ็น : https://<your-space>.hf.space/v1/
|
| 105 |
+
# VS Code IDE : https://<your-space>.hf.space/ide/
|
| 106 |
+
#
|
| 107 |
+
# ๅฟ
ๅกซ HF Space Secrets๏ผ
|
| 108 |
+
# HF_TOKEN โโ HuggingFace ่ฎฟ้ฎไปค็๏ผwrite ๆ้๏ผ
|
| 109 |
+
# HF_DATASET โโ Dataset repo ID๏ผๆ ผๅผ๏ผusername/dataset-name
|
| 110 |
+
#
|
| 111 |
+
# ๅฏ้ Secrets๏ผ
|
| 112 |
+
# CHAT2API_API_KEY โโ Chat2API ่ฎฟ้ฎๅฏ้ฅ๏ผ็็ฉบๅๆ ้่ฎค่ฏ๏ผ
|
| 113 |
+
# CODE_SERVER_PASSWORD โโ VS Code IDE ็ปๅฝๅฏ็ ๏ผ้ป่ฎค๏ผchangeme123!๏ผ
|
| 114 |
+
# IDE_IDLE_MINUTES โโ IDE ้ฒ็ฝฎ่ชๅจๅ
ณ้ญๅ้ๆฐ๏ผ้ป่ฎค๏ผ30๏ผ
|
| 115 |
+
# PROVIDER_1_TYPE / PROVIDER_1_TOKEN โโ ้ขๆณจๅ
ฅๆๅกๅ๏ผๆๅค 10 ไธช๏ผ
|
| 116 |
+
# LB_STRATEGY โโ ่ด่ฝฝๅ่กก็ญ็ฅ๏ผround-robin / fill-first / failover
|
| 117 |
+
# FORCE_RESTORE โโ ่ฎพไธบ true ๅผบๅถไป Dataset ่ฆ็ๆขๅค
|
| 118 |
+
|
| 119 |
+
CMD ["/usr/local/bin/start-chat2api"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|