Spaces:
Sleeping
Sleeping
| import os | |
| import shlex | |
| import subprocess | |
| from typing import Tuple | |
| from flask import Flask, render_template_string, request | |
| app = Flask(__name__) | |
| ALLOWED_COMMANDS = { | |
| "status": ["openclaw", "status"], | |
| "onboard": ["openclaw", "onboard"], | |
| "gateway": ["openclaw", "gateway"], | |
| "tui": ["openclaw", "tui"], | |
| "dashboard": ["openclaw", "dashboard"], | |
| } | |
| HTML = """ | |
| <!doctype html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8"> | |
| <title>OpenClaw Docker Space</title> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <style> | |
| body { | |
| font-family: Arial, sans-serif; | |
| max-width: 900px; | |
| margin: 40px auto; | |
| padding: 0 16px; | |
| } | |
| h1 { margin-bottom: 8px; } | |
| .muted { color: #666; margin-bottom: 24px; } | |
| form { margin-bottom: 24px; } | |
| button { | |
| margin: 6px 8px 6px 0; | |
| padding: 10px 14px; | |
| cursor: pointer; | |
| } | |
| pre { | |
| background: #111; | |
| color: #eee; | |
| padding: 16px; | |
| border-radius: 8px; | |
| overflow-x: auto; | |
| white-space: pre-wrap; | |
| } | |
| .row { | |
| display: flex; | |
| gap: 12px; | |
| align-items: center; | |
| flex-wrap: wrap; | |
| margin-bottom: 16px; | |
| } | |
| input[type=text] { | |
| flex: 1; | |
| min-width: 280px; | |
| padding: 10px; | |
| } | |
| .warn { | |
| background: #fff4e5; | |
| border: 1px solid #f0c36d; | |
| padding: 12px; | |
| border-radius: 8px; | |
| margin-bottom: 20px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>OpenClaw Docker Space</h1> | |
| <div class="muted"> | |
| Runs supported <code>openclaw</code> commands inside this Hugging Face Docker Space. | |
| </div> | |
| <div class="warn"> | |
| Only a fixed allowlist of commands can be run. Arbitrary shell execution is disabled. | |
| </div> | |
| <form method="post"> | |
| <div> | |
| {% for key in commands %} | |
| <button type="submit" name="command" value="{{ key }}">{{ key }}</button> | |
| {% endfor %} | |
| </div> | |
| </form> | |
| <form method="post"> | |
| <div class="row"> | |
| <input type="text" name="custom_command" placeholder="Example: openclaw status" /> | |
| <button type="submit">Run custom command</button> | |
| </div> | |
| </form> | |
| {% if executed %} | |
| <h3>Executed</h3> | |
| <pre>{{ executed }}</pre> | |
| <h3>Exit code</h3> | |
| <pre>{{ returncode }}</pre> | |
| <h3>Output</h3> | |
| <pre>{{ output }}</pre> | |
| {% endif %} | |
| </body> | |
| </html> | |
| """ | |
| def normalize_custom_command(text: str): | |
| text = (text or "").strip() | |
| if not text: | |
| return None | |
| try: | |
| parts = shlex.split(text) | |
| except ValueError: | |
| return None | |
| for allowed in ALLOWED_COMMANDS.values(): | |
| if parts == allowed: | |
| return allowed | |
| return None | |
| def run_command(cmd: list[str]) -> Tuple[str, int]: | |
| env = os.environ.copy() | |
| env["PYTHONUNBUFFERED"] = "1" | |
| try: | |
| result = subprocess.run( | |
| cmd, | |
| stdout=subprocess.PIPE, | |
| stderr=subprocess.STDOUT, | |
| text=True, | |
| timeout=120, | |
| env=env, | |
| cwd="/app", | |
| ) | |
| return result.stdout, result.returncode | |
| except FileNotFoundError: | |
| return "Error: `openclaw` is not installed in the container.", 127 | |
| except subprocess.TimeoutExpired: | |
| return "Error: command timed out after 120 seconds.", 124 | |
| except Exception as e: | |
| return f"Unexpected error: {e}", 1 | |
| def index(): | |
| executed = "" | |
| output = "" | |
| returncode = "" | |
| if request.method == "POST": | |
| selected = request.form.get("command", "").strip() | |
| custom = request.form.get("custom_command", "").strip() | |
| cmd = None | |
| if selected in ALLOWED_COMMANDS: | |
| cmd = ALLOWED_COMMANDS[selected] | |
| elif custom: | |
| cmd = normalize_custom_command(custom) | |
| if cmd is None: | |
| executed = custom or selected or "(none)" | |
| output = ( | |
| "Rejected command.\n\n" | |
| "Allowed commands are:\n" | |
| + "\n".join(" ".join(v) for v in ALLOWED_COMMANDS.values()) | |
| ) | |
| returncode = 400 | |
| else: | |
| executed = " ".join(cmd) | |
| output, returncode = run_command(cmd) | |
| return render_template_string( | |
| HTML, | |
| commands=ALLOWED_COMMANDS.keys(), | |
| executed=executed, | |
| output=output, | |
| returncode=returncode, | |
| ) | |
| if __name__ == "__main__": | |
| port = int(os.environ.get("PORT", "7860")) | |
| app.run(host="0.0.0.0", port=port, debug=False) |