/ examples / desktop / main.py
main.py
  1  # Copyright 2025 Alibaba Group Holding Ltd.
  2  #
  3  # Licensed under the Apache License, Version 2.0 (the "License");
  4  # you may not use this file except in compliance with the License.
  5  # You may obtain a copy of the License at
  6  #
  7  #     http://www.apache.org/licenses/LICENSE-2.0
  8  #
  9  # Unless required by applicable law or agreed to in writing, software
 10  # distributed under the License is distributed on an "AS IS" BASIS,
 11  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 12  # See the License for the specific language governing permissions and
 13  # limitations under the License.
 14  
 15  import asyncio
 16  import os
 17  from datetime import timedelta
 18  
 19  from opensandbox import Sandbox
 20  from opensandbox.config import ConnectionConfig
 21  from opensandbox.models.execd import RunCommandOpts
 22  
 23  
 24  def _required_env(name: str) -> str:
 25      value = os.getenv(name)
 26      if not value:
 27          raise RuntimeError(f"{name} is required")
 28      return value
 29  
 30  
 31  async def _print_logs(label: str, execution) -> None:
 32      for msg in execution.logs.stdout:
 33          print(f"[{label} stdout] {msg.text}")
 34      for msg in execution.logs.stderr:
 35          print(f"[{label} stderr] {msg.text}")
 36      if execution.error:
 37          print(f"[{label} error] {execution.error.name}: {execution.error.value}")
 38  
 39  
 40  async def main() -> None:
 41      domain = os.getenv("SANDBOX_DOMAIN", "localhost:8080")
 42      api_key = os.getenv("SANDBOX_API_KEY")
 43      image = os.getenv(
 44          "SANDBOX_IMAGE",
 45          "opensandbox/desktop:latest",
 46      )
 47      python_version = os.getenv("PYTHON_VERSION", "3.11")
 48      vnc_password = _required_env("VNC_PASSWORD")
 49  
 50      config = ConnectionConfig(
 51          domain=domain,
 52          api_key=api_key,
 53          request_timeout=timedelta(seconds=60),
 54      )
 55  
 56      sandbox = await Sandbox.create(
 57          image,
 58          connection_config=config,
 59          env={
 60              "PYTHON_VERSION": python_version,
 61              "VNC_PASSWORD": vnc_password,
 62          },
 63      )
 64  
 65      async with sandbox:
 66          # Desktop and VNC components are pre-installed in the image, just start them
 67          # Start virtual display, window manager, and VNC server (in background)
 68          xvfb_exec = await sandbox.commands.run(
 69              "Xvfb :0 -screen 0 1280x800x24",
 70              opts=RunCommandOpts(background=True),
 71          )
 72          await _print_logs("xvfb", xvfb_exec)
 73  
 74          # Start XFCE session (provides panel, file manager, terminal)
 75          xfce_exec = await sandbox.commands.run(
 76              "DISPLAY=:0 dbus-launch startxfce4",
 77              opts=RunCommandOpts(background=True),
 78          )
 79          await _print_logs("xfce", xfce_exec)
 80  
 81          vnc_exec = await sandbox.commands.run(
 82              "x11vnc -display :0 "
 83              "-passwd \"$VNC_PASSWORD\" "
 84              "-forever -shared -rfbport 5900",
 85              opts=RunCommandOpts(background=True),
 86          )
 87          await _print_logs("x11vnc", vnc_exec)
 88  
 89          # Start noVNC/websockify to expose VNC over WebSocket/HTTP
 90          novnc_exec = await sandbox.commands.run(
 91              "/usr/bin/websockify --web=/usr/share/novnc 6080 localhost:5900",
 92              opts=RunCommandOpts(background=True),
 93          )
 94          await _print_logs("novnc", novnc_exec)
 95  
 96          endpoint_vnc = await sandbox.get_endpoint(5900)
 97          endpoint_novnc = await sandbox.get_endpoint(6080)
 98  
 99          # Build noVNC URL with host/port/path for routed endpoint, e.g., host:port/proxy/6080
100          novnc_host_port, novnc_path = endpoint_novnc.endpoint.split("/", 1)
101          novnc_host, novnc_port = novnc_host_port.split(":")
102          novnc_url = (
103              f"http://{endpoint_novnc.endpoint}/vnc.html"
104              f"?host={novnc_host}&port={novnc_port}&path={novnc_path}"
105          )
106  
107          print("\nVNC endpoint (native clients):")
108          print(f"  {endpoint_vnc.endpoint}")
109          print(f"Password: {vnc_password}")
110  
111          print("\nnoVNC (browser):")
112          print(f"  {novnc_url}")
113          print(f"Password: {vnc_password}")
114  
115          print("\nKeeping sandbox alive for 5 minutes. Press Ctrl+C to exit sooner.")
116          try:
117              await asyncio.sleep(300)
118          except KeyboardInterrupt:
119              print("Stopping...")
120          finally:
121              await sandbox.kill()
122  
123  
124  if __name__ == "__main__":
125      asyncio.run(main())