diff --git a/ailab/container.py b/ailab/container.py index f93aa62..6257b41 100644 --- a/ailab/container.py +++ b/ailab/container.py @@ -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 = { diff --git a/ailab/installers/openclaw.py b/ailab/installers/openclaw.py index 7ba6438..668183c 100644 --- a/ailab/installers/openclaw.py +++ b/ailab/installers/openclaw.py @@ -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) @@ -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, @@ -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, @@ -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, @@ -299,7 +306,7 @@ def _run_onboard( """ container_exec( cname, - ["bash", "-c", script], + ["bash", "-lc", script], uid=uid, gid=gid, env=env, check=False, @@ -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):