Archived
feat(docker): add fail-closed host profiles
This commit is contained in:
+68
-34
@@ -13,12 +13,11 @@ No prebuilt image or registry is required in either mode.
|
||||
|
||||
The Docker setup is for trusted single-user or trusted-admin environments. It is not a sandbox and it is not suitable for untrusted multi-tenant use.
|
||||
|
||||
By design, the runtime containers get deliberate host access so PI WEB agents can work on real server paths:
|
||||
By design, the runtime containers get deliberate host access so PI WEB agents can work on real host paths:
|
||||
|
||||
- `/var/run/docker.sock` is mounted into the containers. The Docker socket is root-equivalent on the host.
|
||||
- `/srv`, `/opt`, and `/home` are mounted read/write.
|
||||
- `/` is mounted read-only at `/host` for inspection.
|
||||
- `hostexec` can start a temporary privileged helper container and run explicit commands in the host namespaces. Commands run as the container user by default, and `hostexec --root` can still run explicit administrative commands as root.
|
||||
- `/var/run/docker.sock` is mounted into the containers. The Docker socket is root-equivalent on the Docker host.
|
||||
- On native Linux Docker Engine, existing `/home`, `/srv`, and `/opt` paths are mounted read/write, `/` is mounted read-only at `/host` for inspection, and `hostexec` can run explicit commands in the Linux host namespaces.
|
||||
- On Docker Desktop for Mac, existing `/Users`, `/Volumes`, and `/private` paths are mounted read/write. `hostexec` is disabled because Docker Desktop containers run inside a Linux VM and cannot enter native macOS namespaces.
|
||||
|
||||
Only install this on machines where the PI WEB user, the selected workspaces, and the browser/API clients are trusted. Review scripts before piping them to `sh` if you do not already trust this repository.
|
||||
|
||||
@@ -32,10 +31,15 @@ The web port is bound to `127.0.0.1` by default. Do **not** expose PI WEB direct
|
||||
|
||||
Prerequisites:
|
||||
|
||||
- Docker Engine with the Compose plugin (`docker compose`) or `docker-compose`;
|
||||
- one supported Docker host profile:
|
||||
- native Linux Docker Engine using the local `/var/run/docker.sock`; or
|
||||
- Docker Desktop for Mac;
|
||||
- Docker Compose through the `docker compose` plugin or `docker-compose`;
|
||||
- a user that can talk to the Docker daemon;
|
||||
- `curl` or `wget` for the one-liner installer.
|
||||
|
||||
The installer fails closed on unknown or unsupported Docker setups, such as remote Docker contexts, `DOCKER_HOST` overrides outside the supported local Unix socket, rootless/alternate Linux sockets, Docker Desktop for Linux, Colima, or OrbStack. It prints the detected host OS, Docker context, endpoint, `DOCKER_HOST`, socket source, and Docker OS before exiting, and it does not recreate services.
|
||||
|
||||
Install or update with the same command:
|
||||
|
||||
```bash
|
||||
@@ -96,6 +100,8 @@ Common environment variables written to `.env`:
|
||||
| `PI_WEB_UID`, `PI_WEB_GID` | user/group used by the runtime containers and the image's `pi-web` account |
|
||||
| `DOCKER_GID` | extra group used for Docker socket access |
|
||||
| `PI_WEB_DOCKER_DATA_DIR` | persistent data bind mount |
|
||||
| `PI_WEB_DOCKER_HOST_PROFILE`, `HOSTEXEC_MODE` | detected host profile and host-command capability toggle |
|
||||
| `PI_WEB_DOCKER_EXTRA_HOST_PATHS` | optional whitespace-separated existing absolute paths to bind-mount read/write at the same path |
|
||||
| `PI_WEB_BIND_ADDR`, `PI_WEB_PORT` | host bind address and port |
|
||||
| `PI_WEB_VERSION` | npm version/range for `@jmfederico/pi-web` |
|
||||
| `PI_VERSION` | npm version/range for `@earendil-works/pi-coding-agent` |
|
||||
@@ -106,7 +112,9 @@ Common environment variables written to `.env`:
|
||||
| `PI_WEB_IMAGE` | local image tag to build and run |
|
||||
| `HOSTEXEC_IMAGE` | helper image used by `hostexec` |
|
||||
|
||||
Host-derived IDs are refreshed on rerun unless you explicitly override them. User-facing values such as data directory, bind address, port, image names, upload limit, base image, Node.js settings, extra packages, and version pins are preserved from an existing `.env` unless you pass a flag or environment override.
|
||||
Host-derived IDs and the Docker host profile are refreshed on rerun unless you explicitly override the IDs. User-facing values such as data directory, bind address, port, image names, upload limit, extra host paths, base image, Node.js settings, extra packages, and version pins are preserved from an existing `.env` unless you pass a flag or environment override.
|
||||
|
||||
The installer also writes a generated `compose.override.yml` in the install directory. Docker Compose loads it automatically for ordinary `docker compose ...` commands run from that directory; re-run the installer instead of editing that generated file by hand.
|
||||
|
||||
### Base image and tooling
|
||||
|
||||
@@ -204,9 +212,11 @@ If you use a reverse proxy, keep the container bound to localhost or a private a
|
||||
|
||||
## `hostexec` examples
|
||||
|
||||
`hostexec [--root] <command...>` is the only host command bridge provided by this Docker setup. It intentionally does not abstract package managers or detect distributions. By default, commands run as the same numeric user/group as the PI WEB container. Use `--root` only for administrative host commands.
|
||||
`hostexec [--root] <command...>` is the native Linux host command bridge provided by this Docker setup. It is enabled only for the `linux-native-docker` profile and intentionally does not abstract package managers or detect distributions. By default, commands run as the same numeric user/group as the PI WEB container. Use `--root` only for administrative host commands.
|
||||
|
||||
Run it from a PI WEB session, a PI WEB terminal, or by execing into the runtime container:
|
||||
On Docker Desktop for Mac, `hostexec` exits with a clear disabled message because the Docker daemon and containers run inside a Linux VM, not in native macOS namespaces. Docker CLI and Docker Compose commands still work through the mounted Docker socket.
|
||||
|
||||
Run it from a PI WEB session, a PI WEB terminal, or by execing into the runtime container on native Linux:
|
||||
|
||||
```bash
|
||||
hostexec uname -a
|
||||
@@ -223,7 +233,7 @@ cd ~/.local/share/pi-web-docker
|
||||
docker compose exec web hostexec uname -a
|
||||
```
|
||||
|
||||
`hostexec` starts a temporary privileged helper container through the mounted Docker socket, enters the host namespaces with `nsenter`, and runs exactly the command you passed. Treat it like privileged host access even when the final command drops back to the container user.
|
||||
On native Linux, `hostexec` starts a temporary privileged helper container through the mounted Docker socket, enters the host namespaces with `nsenter`, and runs exactly the command you passed. Treat it like privileged host access even when the final command drops back to the container user.
|
||||
|
||||
## Development Docker setup
|
||||
|
||||
@@ -232,24 +242,42 @@ Use this mode when developing PI WEB from this checkout. It bind-mounts the sour
|
||||
- `sessiond` runs `npm run start:sessiond` as the long-lived owner of Pi agent runtimes;
|
||||
- `web` runs `npm run dev:web` and `npm run dev:client` so API, plugin, and Vite changes can autoreload without restarting `sessiond`.
|
||||
|
||||
From the repository root:
|
||||
From the repository root, use the dev Compose wrapper so the same fail-closed host profile detection is applied as runtime mode:
|
||||
|
||||
```bash
|
||||
export PI_WEB_UID=$(id -u)
|
||||
export PI_WEB_GID=$(id -g)
|
||||
export DOCKER_GID=$(stat -c '%g' /var/run/docker.sock)
|
||||
# Optional; this is also the default dev data path.
|
||||
export PI_WEB_DOCKER_DATA_DIR=${PI_WEB_DOCKER_DATA_DIR:-$HOME/.local/share/pi-web-docker/data}
|
||||
mkdir -p "$PI_WEB_DOCKER_DATA_DIR"
|
||||
|
||||
docker compose -f docker/compose.dev.yml up --build
|
||||
./docker/scripts/docker-compose-dev up --build
|
||||
```
|
||||
|
||||
If you already ran the runtime installer, you can reuse its `.env` so dev mode gets the same UID/GID, Docker group, ports, and data directory:
|
||||
The wrapper creates `.pi-web/docker-compose-dev.local.env` on first run, writes `.pi-web/docker-compose-dev.generated.env` and `.pi-web/docker-compose-dev.host.generated.yml`, then runs Docker Compose with `docker/compose.dev.yml` plus that generated host override. Edit only the `.local.env` file for persistent dev settings; the `.generated.env` and `.host.generated.yml` files are refreshed by the wrapper.
|
||||
|
||||
Values used by the wrapper are resolved in this order:
|
||||
|
||||
1. current shell environment, for this run only;
|
||||
2. `.pi-web/docker-compose-dev.local.env`;
|
||||
3. runtime installer env, usually `$HOME/.local/share/pi-web-docker/.env`;
|
||||
4. built-in defaults.
|
||||
|
||||
If you already ran the runtime installer, dev mode therefore reuses defaults such as UID/GID, Docker group, data directory, extra host paths, image build inputs, upload limit, and bind address unless you set a more specific value in the shell or `.local.env`. If an older `.pi-web/docker-compose-dev.env` exists, the first run copies its dev bind/port values into `.local.env` so previous local exposure settings are easy to see and edit.
|
||||
|
||||
To expose the dev API and Vite UI beyond localhost persistently, edit `.pi-web/docker-compose-dev.local.env`:
|
||||
|
||||
```dotenv
|
||||
PI_WEB_DEV_API_BIND_ADDR=0.0.0.0
|
||||
PI_WEB_DEV_BIND_ADDR=0.0.0.0
|
||||
```
|
||||
|
||||
For temporary overrides, prefix the wrapper command:
|
||||
|
||||
```bash
|
||||
docker compose --env-file "$HOME/.local/share/pi-web-docker/.env" \
|
||||
-f docker/compose.dev.yml up --build
|
||||
PI_WEB_DEV_API_BIND_ADDR=0.0.0.0 \
|
||||
PI_WEB_DEV_BIND_ADDR=0.0.0.0 \
|
||||
./docker/scripts/docker-compose-dev up -d --build
|
||||
```
|
||||
|
||||
You can run the dev stack in the background with:
|
||||
|
||||
```bash
|
||||
./docker/scripts/docker-compose-dev up -d --build
|
||||
```
|
||||
|
||||
Open the Vite UI at <http://127.0.0.1:8505>. The dev API is published on <http://127.0.0.1:8504>.
|
||||
@@ -257,16 +285,16 @@ Open the Vite UI at <http://127.0.0.1:8505>. The dev API is published on <http:/
|
||||
Useful development commands:
|
||||
|
||||
```bash
|
||||
docker compose -f docker/compose.dev.yml ps
|
||||
docker compose -f docker/compose.dev.yml logs -f web
|
||||
docker compose -f docker/compose.dev.yml restart web
|
||||
docker compose -f docker/compose.dev.yml restart sessiond
|
||||
docker compose -f docker/compose.dev.yml down
|
||||
./docker/scripts/docker-compose-dev ps
|
||||
./docker/scripts/docker-compose-dev logs -f web
|
||||
./docker/scripts/docker-compose-dev restart web
|
||||
./docker/scripts/docker-compose-dev restart sessiond
|
||||
./docker/scripts/docker-compose-dev down
|
||||
```
|
||||
|
||||
Restart `sessiond` manually after changes that affect `src/server/sessiond.ts`, daemon ownership, or session-daemon-only code paths. Restarting only `web` is enough for ordinary API/client/plugin development reloads.
|
||||
|
||||
The dev setup intentionally has the same Docker socket and broad host mounts as the runtime setup. The same trust warnings apply.
|
||||
The dev setup intentionally has the same Docker socket and profile-specific host mounts as the runtime setup. The same trust warnings apply.
|
||||
|
||||
On startup, a short `data-init` service creates the shared `/data` subdirectories and gives them to `PI_WEB_UID:PI_WEB_GID`. This handles the common Flatcar/Docker case where a missing bind-mount directory is created as root by the Docker daemon. Because the image also builds its `pi-web` account with those IDs, rebuild the image if you change `PI_WEB_UID` or `PI_WEB_GID`.
|
||||
|
||||
@@ -288,14 +316,14 @@ Set `PI_WEB_DOCKER_DATA_DIR=/some/path` for both modes if you want that shared d
|
||||
|
||||
Use this shared directory to switch between runtime and dev mode, not to run both at the same time. Stop one Compose stack before starting the other so two session daemons do not share the same socket/state directory concurrently.
|
||||
|
||||
For sessions to appear under the same workspace in both modes, use the same project path in PI WEB. On Flatcar, prefer host-mounted paths such as `/home/core/<repo>`, `/srv/<project>`, or `/opt/<project>`. The dev container also exposes this checkout as `/workspace` so the PI WEB dev server can run from it, but sessions started against `/workspace` are organized under that different working-directory path and will not line up with runtime sessions for `/home/core/<repo>`.
|
||||
For sessions to appear under the same workspace in both modes, use the same project path in PI WEB. On Linux, prefer host-mounted paths such as `/home/core/<repo>`, `/srv/<project>`, or `/opt/<project>`. On Mac, prefer paths under `/Users/<you>/...`. The dev container also exposes this checkout as `/workspace` so the PI WEB dev server can run from it, but sessions started against `/workspace` are organized under that different working-directory path and will not line up with runtime sessions for the host-mounted path.
|
||||
|
||||
When `package-lock.json` changes, rebuild the dev image and recreate the `node_modules` volume so the bind-mounted checkout sees the new dependency tree:
|
||||
|
||||
```bash
|
||||
docker compose -f docker/compose.dev.yml down
|
||||
./docker/scripts/docker-compose-dev down
|
||||
docker volume rm pi-web-dev_node_modules
|
||||
docker compose -f docker/compose.dev.yml up --build
|
||||
./docker/scripts/docker-compose-dev up --build
|
||||
```
|
||||
|
||||
## Local checkout validation
|
||||
@@ -309,11 +337,17 @@ PI_WEB_DOCKER_HOME="$(mktemp -d)" \
|
||||
sh docker/install.sh
|
||||
```
|
||||
|
||||
For Compose validation:
|
||||
For Compose validation after generating host overrides:
|
||||
|
||||
```bash
|
||||
docker compose -f docker/compose.yml config
|
||||
docker compose -f docker/compose.dev.yml config
|
||||
tmp_home=$(mktemp -d)
|
||||
PI_WEB_DOCKER_SKIP_COMPOSE=1 \
|
||||
PI_WEB_DOCKER_ASSET_DIR="$PWD/docker" \
|
||||
PI_WEB_DOCKER_HOME="$tmp_home" \
|
||||
sh docker/install.sh
|
||||
|
||||
docker compose -f "$tmp_home/compose.yml" -f "$tmp_home/compose.override.yml" config
|
||||
./docker/scripts/docker-compose-dev config
|
||||
docker build --check -f docker/Dockerfile docker
|
||||
docker build --check -f docker/Dockerfile.dev .
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user