openclaw / app.py
Marvin Wiesner
Create app.py
4e5f741 verified
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
@app.route("/", methods=["GET", "POST"])
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)