mirror of
https://github.com/joleuger/vuinputd.git
synced 2026-07-17 16:36:03 +00:00
Improved usage guide for nixos
This commit is contained in:
parent
2c8d0f9af1
commit
ef060ce9d6
1 changed files with 276 additions and 4 deletions
|
|
@ -1,13 +1,285 @@
|
|||
# Usage Guide for NixOS
|
||||
|
||||
This guide will explain how to use `vuinputd` on NixOS. For general remarks, please refer to [USAGE.md](USAGE.md).
|
||||
This guide explains how to set up and use `vuinputd` on NixOS.
|
||||
For general remarks about `vuinputd` and how it works, please refer to [USAGE.md](USAGE.md).
|
||||
|
||||
> **Status:** NixOS is one of the primary target platforms for `vuinputd`. Native packaging
|
||||
> is planned; for now, the configuration below builds `vuinputd` directly from source as part
|
||||
> of the NixOS system.
|
||||
|
||||
Currently, this is WIP. The plan is to make NixOS one of the main platforms for vuinputd.
|
||||
---
|
||||
|
||||
## Configurations in the community
|
||||
## Configurations in the Community
|
||||
|
||||
The following community members have shared their NixOS configurations including `vuinputd`:
|
||||
|
||||
* [ShaneTRS](https://github.com/ShaneTRS/nixos-config/)
|
||||
* [griffi-gh](https://github.com/girl-pp-ua/nixos-infra/)
|
||||
* [Markus328](https://github.com/joleuger/vuinputd/issues/14)
|
||||
|
||||
Feel free to create a github issue or a PR to add your configuration or your remarks.
|
||||
Feel free to open a GitHub issue or pull request to add your own configuration or remarks.
|
||||
|
||||
---
|
||||
|
||||
## NixOS Configuration
|
||||
|
||||
The example below is a self-contained NixOS module that:
|
||||
|
||||
- Builds `vuinputd` from source using `rustPlatform.buildRustPackage`
|
||||
- Installs the required udev rules and hwdb entries
|
||||
- Runs `vuinputd` as a systemd service with a tmpfs for the container-scoped `/dev/input` tree
|
||||
- Applies the correct permissions to `/dev/vuinput` after the daemon starts
|
||||
|
||||
Add the module to your `configuration.nix` imports and run `nixos-rebuild switch`.
|
||||
|
||||
```nix
|
||||
{ config, pkgs, lib, ... }:
|
||||
let
|
||||
vuinputd = pkgs.rustPlatform.buildRustPackage {
|
||||
pname = "vuinputd";
|
||||
version = "0.3.2-git";
|
||||
|
||||
buildType = "debug";
|
||||
|
||||
nativeBuildInputs = [
|
||||
pkgs.pkg-config
|
||||
pkgs.rustPlatform.bindgenHook
|
||||
];
|
||||
|
||||
buildInputs = [ pkgs.udev pkgs.fuse3 ];
|
||||
|
||||
src = pkgs.fetchFromGitHub {
|
||||
owner = "joleuger";
|
||||
repo = "vuinputd";
|
||||
rev = "8c40fdc12005319ea16dceb752a8822abfc6039a";
|
||||
hash = "sha256-8Q34B04BngZqRLyixeFq8F1t5wFnk6JpaG3EEbgKRcU=";
|
||||
};
|
||||
|
||||
cargoHash = "sha256-nJw9bRh6Yn9g1H5SeoT6zxgZLCqV3AtAs9gMfE+P+CU=";
|
||||
|
||||
# Recent versions of fuse3 expose additional libfuse_* types that bindgen
|
||||
# needs to allowlist alongside the standard fuse_* types.
|
||||
postPatch = ''
|
||||
substituteInPlace cuse-lowlevel/build.rs \
|
||||
--replace-fail '.allowlist_type("(?i)^fuse.*")' '.allowlist_type("(?i)^(fuse|libfuse).*")'
|
||||
'';
|
||||
|
||||
postInstall = ''
|
||||
mkdir -p $out/lib/udev/rules.d
|
||||
mkdir $out/lib/udev/hwdb.d
|
||||
cp vuinputd/udev/*.rules $out/lib/udev/rules.d/
|
||||
cp vuinputd/udev/*.hwdb $out/lib/udev/hwdb.d/
|
||||
'';
|
||||
};
|
||||
in
|
||||
{
|
||||
environment.systemPackages = with pkgs; [
|
||||
vuinputd
|
||||
bubblewrap # required for running containerized applications via bwrap
|
||||
];
|
||||
|
||||
# Main vuinputd daemon.
|
||||
# Before starting, a tmpfs is mounted at /run/vuinputd/vuinput/dev-input.
|
||||
# This directory serves as the container-scoped /dev/input tree: input devices
|
||||
# created by vuinputd are placed here instead of the host's /dev/input, so
|
||||
# that containers see only their own devices.
|
||||
systemd.services.vuinputd = {
|
||||
enable = true;
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
unitConfig = {
|
||||
Description = "Virtual input (/dev/vuinput) daemon";
|
||||
};
|
||||
serviceConfig = {
|
||||
Type = "exec";
|
||||
ExecStartPre = pkgs.writeShellScript "mount-tmpfs-dev-input" ''
|
||||
mkdir -p /run/vuinputd/vuinput/dev-input
|
||||
${pkgs.util-linux}/bin/mount -t tmpfs -o rw,dev,nosuid tmpfs /run/vuinputd/vuinput/dev-input
|
||||
'';
|
||||
ExecStart = "${lib.getExe vuinputd} --major 120 --minor 414795 --placement on-host";
|
||||
ExecStopPost = pkgs.writeShellScript "umount-dev-input" ''
|
||||
${pkgs.util-linux}/bin/umount /run/vuinputd/vuinput/dev-input
|
||||
'';
|
||||
Restart = "on-failure";
|
||||
# Required to allow vuinputd to access character devices (uinput, CUSE).
|
||||
DeviceAllow = "char-* rwm";
|
||||
Environment = [
|
||||
"RUST_LOG=debug"
|
||||
];
|
||||
};
|
||||
};
|
||||
|
||||
# vuinputd creates /dev/vuinput via CUSE. The device initially has restrictive
|
||||
# permissions, so a one-shot service applies chmod 666 shortly after startup.
|
||||
# A proper udev-based solution is planned to replace this workaround.
|
||||
systemd.services.vuinputd-chmod = {
|
||||
unitConfig.Description = "Chmod 666 /dev/vuinput";
|
||||
wantedBy = [ "vuinputd.service" ];
|
||||
after = [ "vuinputd.service" ];
|
||||
serviceConfig = {
|
||||
ExecStart = pkgs.writeShellScript "chmod-vuinput" ''
|
||||
sleep 2 && chmod 666 /dev/vuinput
|
||||
'';
|
||||
};
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
### Key Configuration Notes
|
||||
|
||||
**`--major` and `--minor`**
|
||||
These are the device numbers assigned to the virtual `/dev/uinput` character device exposed
|
||||
inside the container. The values `120` and `414795` are chosen to avoid conflicts with
|
||||
real devices on the host. Refer to [USAGE.md](USAGE.md) for details on choosing these values.
|
||||
|
||||
**`--placement on-host`**
|
||||
Tells `vuinputd` to place the resulting `/dev/input/event*` devices on the host side (under
|
||||
the tmpfs at `/run/vuinputd/vuinput/dev-input`) rather than directly in the host's `/dev/input`.
|
||||
This is what enables per-container input isolation.
|
||||
|
||||
**`DeviceAllow = "char-* rwm"`**
|
||||
`vuinputd` needs access to `/dev/uinput` (to create real input devices on the host) and to
|
||||
the CUSE subsystem (to expose the virtual `/dev/uinput` inside containers). Both are character
|
||||
devices, so this broad allowlist is currently required. Reducing the attack surface here is a
|
||||
[planned hardening step](https://github.com/joleuger/vuinputd/blob/main/docs/DESIGN.md).
|
||||
|
||||
**`--device-policy`**
|
||||
The `ExecStart` line can be extended with a `--device-policy` flag to control which input
|
||||
capabilities and events the daemon exposes to containerized applications:
|
||||
|
||||
| Policy | Effect |
|
||||
|---|---|
|
||||
| `none` | All capabilities allowed; no filtering. Useful for debugging. |
|
||||
| `mute-sys-rq` | Blocks SysRq key handling. All other input passes through. **(default)** |
|
||||
| `sanitized` | Keyboards and mice only; filters SysRq and VT-switching combos. Recommended for desktop/streaming workloads. |
|
||||
| `strict-gamepad` | Gamepad-like devices only; blocks keyboards and mice entirely. |
|
||||
|
||||
For example, to use the recommended policy for a Sunshine streaming container:
|
||||
|
||||
```nix
|
||||
ExecStart = "${lib.getExe vuinputd} --major 120 --minor 414795 --placement on-host --device-policy sanitized";
|
||||
```
|
||||
|
||||
See [USAGE.md](USAGE.md) for a full description of each policy.
|
||||
|
||||
**The `vuinputd-chmod` service**
|
||||
The CUSE device `/dev/vuinput` is created by the kernel with root-only permissions. Until
|
||||
a proper udev rule handles this, a small one-shot service applies `chmod 666` two seconds
|
||||
after the daemon starts. This is a known rough edge and will be improved.
|
||||
|
||||
---
|
||||
|
||||
## Running a Containerized Application
|
||||
|
||||
Once `vuinputd` is running, start a containerized application by binding the virtual devices
|
||||
into its namespace. The example below uses `bwrap` (Bubblewrap) as a lightweight container:
|
||||
|
||||
```bash
|
||||
bwrap \
|
||||
--unshare-net \
|
||||
--ro-bind / / \
|
||||
--tmpfs /tmp \
|
||||
--tmpfs /run/udev \
|
||||
--dev-bind /run/vuinputd/vuinput/dev-input /dev/input \
|
||||
--dev-bind /dev/vuinput /dev/uinput \
|
||||
<your-application>
|
||||
```
|
||||
|
||||
The two `--dev-bind` flags are the core of the integration:
|
||||
|
||||
| Bind | Purpose |
|
||||
|---|---|
|
||||
| `/run/vuinputd/vuinput/dev-input` → `/dev/input` | Gives the container its own isolated `/dev/input` tree populated by `vuinputd`. |
|
||||
| `/dev/vuinput` → `/dev/uinput` | Exposes the CUSE-backed virtual `/dev/uinput` at the standard path the application expects. |
|
||||
|
||||
The `--tmpfs /run/udev` flag provides a writable but empty udev runtime directory inside the
|
||||
sandbox. This is sufficient when using `--placement on-host`, because `vuinputd` forwards udev
|
||||
events into the container directly. If you switch to `--placement in-container`, replace this
|
||||
flag with a bind-mount of the actual udev runtime directory instead, and create the required
|
||||
stubs inside the container:
|
||||
|
||||
```bash
|
||||
mkdir -p /run/udev/data/
|
||||
touch /run/udev/control
|
||||
```
|
||||
|
||||
For instructions on testing this setup in an isolated VM, see
|
||||
[Testing vuinputd on NixOS with Incus](https://github.com/joleuger/vuinputd/blob/main/distro-tests/nixos/README.md).
|
||||
|
||||
---
|
||||
|
||||
## Verifying Operation
|
||||
|
||||
To confirm that `vuinputd` and the container integration are working correctly, run the
|
||||
following checks inside the container (install `libinput-tools` and `evtest` if needed):
|
||||
|
||||
```bash
|
||||
# Watch for device creation and input events
|
||||
libinput debug-events
|
||||
|
||||
# Observe udev announcements in a second terminal
|
||||
udevadm monitor -p
|
||||
|
||||
# Read raw events from the input device
|
||||
evtest /dev/input/event*
|
||||
```
|
||||
|
||||
Then trigger some input from within the container (e.g. run a test binary or move a virtual
|
||||
mouse). You should see device creation reported by `libinput` and `udevadm`, and raw event
|
||||
data in `evtest`. On the host, `journalctl -u vuinputd` should show corresponding log lines
|
||||
about device creation and event forwarding.
|
||||
|
||||
For a more detailed walkthrough with example output, see the [Verifying Operation](USAGE.md#7-verifying-operation)
|
||||
section in the main usage guide.
|
||||
|
||||
---
|
||||
|
||||
## Phantom Input Events and VT Handling
|
||||
|
||||
On headless NixOS systems (no active graphical session), the Linux kernel's virtual terminal
|
||||
(VT) layer remains active and continues to process keyboard input. This can cause injected
|
||||
input forwarded by `vuinputd` to reach `getty` login prompts or trigger kernel hotkeys such
|
||||
as `Ctrl+Alt+Fn`.
|
||||
|
||||
The quickest mitigation is to start `vuinputd` with the `--vt-guard` flag:
|
||||
|
||||
```nix
|
||||
ExecStart = "${lib.getExe vuinputd} --major 120 --minor 414795 --placement on-host --vt-guard";
|
||||
```
|
||||
|
||||
`--vt-guard` switches the active VT into graphics mode via a direct ioctl, which disables
|
||||
the kernel keyboard handler for that VT without requiring a compositor or DRM device.
|
||||
|
||||
For a full discussion of all available approaches (including KMSCON and the experimental
|
||||
`fallbackdm`), see the [Phantom Input Events](USAGE.md#8-handling-phantom-input-events-caused-by-vts)
|
||||
section in the main usage guide.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
If `vuinputd` does not behave as expected, refer to
|
||||
[DEBUG.md](https://github.com/joleuger/vuinputd/blob/main/docs/DEBUG.md) for general
|
||||
debugging guidance.
|
||||
|
||||
Common NixOS-specific issues:
|
||||
|
||||
- **CUSE module not loaded:** NixOS should load `cuse` automatically via udev, but if
|
||||
`/dev/vuinput` does not appear after the service starts, run `modprobe cuse` and restart
|
||||
the service.
|
||||
- **`/dev/vuinput` is not accessible:** The `vuinputd-chmod` service applies permissions
|
||||
2 seconds after startup. If it fails, check `systemctl status vuinputd-chmod` and apply
|
||||
`chmod 666 /dev/vuinput` manually for debugging.
|
||||
- **Input devices not visible inside the container:** Verify that
|
||||
`/run/vuinputd/vuinput/dev-input` is mounted as a tmpfs (`mount | grep vuinputd`) and that
|
||||
the `bwrap` `--dev-bind` flags point to the correct paths.
|
||||
- **Read-only filesystem error from `vuinputd`:** If the daemon logs an error like
|
||||
`ReadOnlyFilesystem` when creating a device node, the directory where it tries to write
|
||||
(typically `/dev/input` or `/run`) is not writable inside the container. Ensure the
|
||||
`--dev-bind` and `--tmpfs` flags in your `bwrap` command cover all paths `vuinputd` writes
|
||||
to, or switch to `--placement on-host` so writes happen on the host side instead.
|
||||
|
||||
```
|
||||
Error creating input device /dev/input/event12: Read-only file system
|
||||
```
|
||||
- **Build failures due to bindgen/fuse3 mismatch:** Ensure the `postPatch` block in the
|
||||
derivation is present; it is required for recent versions of `fuse3`.
|
||||
Loading…
Add table
Add a link
Reference in a new issue