Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions ailab/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -802,6 +802,29 @@ def create_container(
"bind": "host",
}

# GPU passthrough: expose host GPUs (and AMD KFD compute device) to the
# container. The "gpu" device type covers /dev/dri render nodes for any
# vendor; /dev/kfd is added separately as a unix-char device because LXD's
# "gpu" type does not include it.
# uid/gid/mode make LXD expose the device nodes inside the container owned
# by the mapped user, so the user account has read/write access without
# needing render/video group membership.
if os.path.exists("/dev/dri"):
devices["gpu"] = {
"type": "gpu",
"uid": str(uid),
"gid": str(gid),
"mode": "0660",
}
if os.path.exists("/dev/kfd"):
devices["kfd"] = {
"type": "unix-char",
"source": "/dev/kfd",
"uid": str(uid),
"gid": str(gid),
"mode": "0660",
}

# ── Build instance config ─────────────────────────────────────────────────
idmap = f"uid {uid} {uid}\ngid {gid} {gid}"
config = {
Expand Down
44 changes: 35 additions & 9 deletions ailab/installers/openclaw.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def install(self, container_name: str):
start_container(cname)

print("Installing openclaw via npm...")
self._npm_install(cname, uid)
self._npm_install(cname, uid, gid, home)

print("Installing openclaw gateway user service...")
self._install_gateway_service(cname, uid, gid, home)
Expand Down Expand Up @@ -97,7 +97,7 @@ def _install_gateway_service(self, cname: str, uid: int, gid: int, home: str):
"""Install openclaw's gateway as a user-level systemd service (unit only; do not enable yet)."""
container_exec(
cname,
["bash", "-c", "openclaw gateway install 2>&1 || true"],
["bash", "-lc", "openclaw gateway install 2>&1 || true"],
uid=uid, gid=gid,
env={"HOME": home},
check=False,
Expand Down Expand Up @@ -192,8 +192,12 @@ def _configure_gateway_env(
The drop-in ensures the gateway service always starts with the correct
token, regardless of how the user session was started.
"""
npm_bin = f"{home}/.npm-global/bin"
env_dir = Path(home) / ".config" / "environment.d"
conf = f"OPENCLAW_GATEWAY_TOKEN={gateway_token}\n"
conf = (
f"OPENCLAW_GATEWAY_TOKEN={gateway_token}\n"
f"NPM_CONFIG_PREFIX={home}/.npm-global\n"
)
# Write environment.d for CLI / login-shell use
container_exec(
cname,
Expand All @@ -202,12 +206,15 @@ def _configure_gateway_env(
env={"HOME": home},
stdin=conf.encode(),
)
# Write a systemd service drop-in so the daemon always has the token,
# Write a systemd service drop-in so the daemon always has the token
# and can find the openclaw binary in the user-local npm prefix,
# even in a lingering session where environment.d may not be sourced.
dropin_dir = Path(home) / ".config" / "systemd" / "user" / "openclaw-gateway.service.d"
dropin = (
"[Service]\n"
f"Environment=OPENCLAW_GATEWAY_TOKEN={gateway_token}\n"
f"Environment=NPM_CONFIG_PREFIX={home}/.npm-global\n"
f'Environment=PATH={npm_bin}:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin\n'
)
container_exec(
cname,
Expand Down Expand Up @@ -299,7 +306,7 @@ def _run_onboard(
"""
container_exec(
cname,
["bash", "-c", script],
["bash", "-lc", script],
uid=uid, gid=gid,
env=env,
check=False,
Expand Down Expand Up @@ -419,12 +426,31 @@ def _install_shell_completion(self, cname: str, uid: int, gid: int, home: str):
stdin=stdout.encode(),
)

def _npm_install(self, cname: str, uid: int):
"""Install openclaw globally via npm inside the container (as root)."""
def _npm_install(self, cname: str, uid: int, gid: int, home: str):
"""Install openclaw via npm into a user-writable prefix."""
prefix = f"{home}/.npm-global"
container_exec(
cname,
["bash", "-c", f"mkdir -p {prefix}"],
uid=uid, gid=gid,
env={"HOME": home},
)
container_exec(
cname,
["npm", "install", "-g", f"--prefix={prefix}", "openclaw"],
uid=uid, gid=gid,
env={"HOME": home},
)
# Ensure the user-local npm bin is on PATH for all login shells
snippet = (
f'\n# npm user-global prefix\n'
f'export NPM_CONFIG_PREFIX="{prefix}"\n'
f'export PATH="{prefix}/bin:$PATH"\n'
)
container_exec(
cname,
["npm", "install", "-g", "openclaw"],
env={"HOME": "/root"},
["bash", "-c", "cat >> /etc/profile.d/ailab-openclaw.sh"],
stdin=snippet.encode(),
)

def _add_port_proxy(self, cname: str):
Expand Down