Guest Daemon Architecture¶
The guest daemon (podbox-guest) runs inside the container. It bridges
container capabilities (notifications, URI opening, clipboard, host execution)
to the host via a Unix socket connection.
Entry Point (entry.rs)¶
The container starts with podbox-guest --entry [<command>...].
-
fork()splits into two processes: -
Child (daemon process): redirects stdio to
/dev/null, then execspodbox-guest --daemon(re-exec). Runs the event loop. -
Parent (shell/command process): if a command was given, execs it via
execv. If empty, execs a login shell ($SHELLor/bin/bash, withargv[0]prefixed by-for login mode). -
The parent replaces itself with the shell/command. The child runs independently as a background daemon with a 5-minute idle timeout.
Daemon Lifecycle (daemon.rs)¶
Startup sequence¶
- Create
/run/podbox/bin/— directory for interceptor symlinks - Check version drift — compare
PODBOX_HOST_VERSIONenv var againstpodbox-guestversion; warn on mismatch - Connect to host socket —
$XDG_RUNTIME_DIR/podbox/<container>.sockwith poll-based retry (3 attempts, 500ms interval, zero CPU) - Handshake — sends capability list (
notify,xdg_open,clipboard,host_exec) to host; host responds with accepted subset - Install interceptors — creates symlinks in
/run/podbox/bin/for each accepted capability - PATH injection — writes
/etc/environment.d/podbox.confthat prepends/run/podbox/bintoPATH - Event loop — polls the host socket for messages
Event loop¶
The event loop is poll()-based on a single file descriptor (the host
socket connection). It uses a 5-minute idle timeout — if no message
arrives, the daemon exits gracefully.
| Event | Action |
|---|---|
Shutdown message |
Exit daemon |
Ping message |
No-op (keepalive) |
None / EOF |
Host disconnected; exit |
POLLHUP / POLLERR |
Host socket hung up; exit |
| Idle timeout (5 min) | No messages received; exit |
EINTR |
Retry poll() |
The daemon consumes 0% CPU when idle — it is parked in the kernel by
poll().
Socket Protocol¶
The daemon connects to the host socket at
$XDG_RUNTIME_DIR/podbox/<container>.sock. Messages are length-prefixed
JSON over a Unix stream socket (see protocol.md for the wire
format).
Handshake¶
→ {"type":"hello","version":"0.1.0","container":"myenv","capabilities":["notify","xdg_open","clipboard","host_exec"]}
← {"type":"hello_ack","accepted":["notify","xdg_open"],"rejected":["clipboard","host_exec"]}
Interceptors¶
Symlink dispatch¶
The daemon creates symlinks in /run/podbox/bin/ pointing to the
podbox-guest binary:
| Symlink | Target | Capability |
|---|---|---|
/run/podbox/bin/notify-send |
podbox-guest |
notify |
/run/podbox/bin/xdg-open |
podbox-guest |
xdg_open |
/run/podbox/bin/podbox-clipboard |
podbox-guest |
clipboard |
/run/podbox/bin/host-exec |
podbox-guest |
host_exec |
The binary detects which name was used to invoke it via argv[0] and
dispatches to the appropriate interceptor module (main.rs).
PATH injection¶
/etc/environment.d/podbox.conf is written with:
This ensures the interceptor symlinks take precedence over system-installed binaries.
Interceptor types¶
| Interceptor | File | What it does |
|---|---|---|
notify-send |
interceptors/notify.rs |
Parses CLI args, sends GuestMessage::Notify to host. Supports --action/-A for action buttons. Waits for NotifyActionResult response when actions are present. |
xdg-open |
interceptors/xdg_open.rs |
Sends URI in GuestMessage::XdgOpen to host |
podbox-clipboard |
interceptors/clipboard.rs |
set: reads stdin, sends ClipboardSet; get: sends ClipboardGet, writes response to stdout |
host-exec |
interceptors/host_exec.rs |
Connects to host socket, sends HostExec with command, then relays HostExecStdout/HostExecStderr/HostExecDone responses to stdout/stderr and exits with the remote exit code |
Each interceptor opens a direct, ephemeral Unix socket connection to the host socket (not the daemon's persistent connection), sends its message, and waits for acknowledgement before exiting.