Rabble is our home server / gaming PC. My goal was to have a machine that could take over the burden of running the various things we run around the house, and also to act as a couch gaming PC with enough oomph to play any game we'd like. Some stuff about it:
This repo is a collection of notes around the planning, building, configuration and maintenance of the machine, mostly for my own records but also as a source if others want to build a similar setup.
I spent a little less than a month putting together the part list, focusing on a high-end-but-still-cost-conscious approach that I later found out mapped almost perfectly onto the Logical Increments 'outstanding' tier.
Not much to say about this; it's a pretty standard PC (despite the absurd case) and goes together without any real fuss. All of the parts other than the CPU cooler would port over to any standard SFF case if you're looking for something more conventional.
We designed the Nook print vinyl sticker & had it printed at StickerYou. The Tom Nook figurine is an Amiibo. We also bought a couple of light bars from AliExpress, and after some failed experiments trying to control them in software, I picked up a standalone RGB controller that is powered directly off the PSU and is stuck on the bottom of the case for the rare time that we actually want to have the RGB lighting on.
As this build is vulnerable to Intel's recent CPU self-destruct bug, the first thing we did at first boot was to update the BIOS to the latest version which is supposedly safe.
It's currently running the following BIOS settings (notes are somewhat terse but you can figure it out):
Many of these can optionally be managed in Linux, see the EFI Notes for details
sudo systemctl enable --now sshdssh-copy-id rabble.localrabble.local and:
echo 'PasswordAuthentication no' > /etc/ssh/sshd_config.d/99-local.confecho 'mat ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/99-localsudo pacman -S docker docker-composesudo systemctl enable --now dockersudo usermod -aG docker $USER (Log out and back in to get docker permissions)sudo pacman -S cachyos-gaming-meta steamsudo pacman -S neovim (pick the default providers for deps)chsh (select /bin/zsh)git clone git@github.com:mtrudel/dotfiles.gitcd dotfiles && ./install.zsh git nvim ssh zshWe set this up in a cabinet next to the TV (an LG OLED77B5PUA) and have it wired up like so:
sudo systemctl set-default multi-user.target2dc8:301c to 2dc8:310a). Handles two cases: bringing the whole session up from a bare console, or just waking an already-running session whose screen has gone DPMS-blank from idle
sudo tee /etc/udev/rules.d/99-8bitdo-tv.rules <<EOF
ACTION=="add", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2dc8", ATTRS{idProduct}=="310a", RUN+="/usr/bin/systemctl --no-block start 8bitdo-gui-wake.service"
EOF
sudo tee /etc/systemd/system/8bitdo-gui-wake.service <<EOF
[Unit]
Description=Switch to graphical session when 8BitDo controller connects
[Service]
Type=oneshot
ExecStart=/usr/bin/touch /run/switch-session-pending
ExecStart=/usr/bin/systemctl isolate graphical.target
ExecStart=-/usr/bin/runuser -u mat -- /bin/bash -c 'XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-0 kscreen-doctor --dpms on'
ExecStart=-/usr/local/bin/tv-ctl on
EOF
sudo systemctl daemon-reload && sudo udevadm control --reload-rules
The controller-wake and the PowerDevil idle-triggered auto-suspend (isolate multi-user.target, see below) can fire in opposite directions at nearly the
same instant. isolate stops everything not required by its destination
target so either one can kill the other as pure collateral. The touch
above is fast enough to complete before any kill, and
session-switch-resume.service below picks up the marker and finishes the
job once the race resolves.
sudo tee /etc/systemd/system/session-switch-resume.service <<EOF
[Unit]
Description=Resume to graphical.target after a manual session switch or a lost isolate race
ConditionPathExists=/run/switch-session-pending
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'rm -f /run/switch-session-pending; systemctl isolate graphical.target'
ExecStart=-/usr/bin/runuser -u mat -- /bin/bash -c 'XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-0 kscreen-doctor --dpms on'
ExecStart=-/usr/local/bin/tv-ctl on
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable session-switch-resume.service
Screen dims after TurnOffDisplayIdleTimeoutSec (in ~/.config/powerdevilrc)
If the screen isn't blanking on schedule, systemctl --user restart plasma-powerdevil.service fixes it.systemd-suspend.service's ExecStart (wrapped in systemd-run --no-block since
isolating directly from within the unit that's running conflicts with itself):
sudo mkdir -p /etc/systemd/system/systemd-suspend.service.d
sudo tee /etc/systemd/system/systemd-suspend.service.d/99-drop-graphical-instead.conf <<EOF
[Service]
ExecStart=
ExecStart=/usr/bin/systemd-run --no-block --collect /usr/bin/systemctl isolate multi-user.target
EOF
sudo systemctl daemon-reload
tv-ctl over websocat/jq/wakeonlan to keep this simple and
dependency free:
sudo pacman -S websocat jq wakeonlansudo mkdir -p /etc/rabble-tv && sudo cp lgtv-manifest.json /etc/rabble-tv/manifest.json
(lgtv-manifest.json is the standard, publicly-reused LG
pairing manifest - the same com.lge.test blob bscpylgtv/lgtv2/etc. all use)sudo install -m 755 tv-ctl /usr/local/bin/tv-ctlsudo mkdir -p /var/lib/rabble-tv && sudo chown mat:mat /var/lib/rabble-tvtv-ctl register once (TV must be on; accept the prompt on-screen within 45s)
to populate /var/lib/rabble-tv/client-keyaudio/setVolume only updates the TV's own internal counter and does nothing
audible once sound is routed to an external soundbar over ARC/eARC - tv-ctl
instead tracks its own last-known level in /var/lib/rabble-tv/volume and
steps to the target via the same relative volumeUp/volumeDown calls the
physical remote's rocker useslogind broadcasts PrepareForSleep on any
suspend (including the fake one above) outside the systemd unit graph, so
NetworkManager briefly drops networking right when tv-ctl needs itgraphical.target:
sudo tee /etc/systemd/system/tv-gui-hook.service <<EOF
[Unit]
Description=Turn TV on/off in sync with entering/leaving the graphical session
[Service]
Type=oneshot
RemainAfterExit=yes
TimeoutStopSec=30
ExecStart=/usr/local/bin/tv-ctl on
ExecStop=/usr/local/bin/tv-ctl off
[Install]
WantedBy=graphical.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable --now tv-gui-hook.service
(TimeoutStopSec=30 overrides this box's unusually short 10s default stop
timeout, giving tv-ctl's retries above room to actually finish)In low power mode (ie: when the display is not running and the GPU is idle), this setup pulls around 33-34W as measured at the wall. At peak gaming it can get up to 400W or more. I don't really care too much about power usage while gaming; that's a time-limited activity and basically par for the course. The far more relevant optimizations are around consumption when the box is idle.
I measured at ~33-34W at the wall with TV plugged in but off, ethernet connected, and USB devices as described below. Conditions: BIOS settings as above (notably RAM settings make a big difference), LTR ignore active, EEE enabled, all docker containers running:
| Component | Draw | Basis |
|---|---|---|
| CPU core (i5-13600K idle) | 3W | RAPL (measured via turbostat on minimal system) |
| CPU package (i5-13600K idle) | 0.5W | RAPL (measured via turbostat on minimal system) |
| GPU (RTX 4070, deep idle) | 3.4W | nvidia-smi (measured) |
| DDR5-4800 2×stick @ 1.1V | 3W | measured by booting with one stick removed |
| USB devices | 2W | measured with USB current meter |
| Fans (3× slow: 341/559/503 RPM) | 0.5W | estimated from spec sheets |
| NVMe SSD (P41 in APST PS4) | 0.5W | estimated from PS4 residency |
| Ethernet (I226-V + EEE) | 1W | measured on base system |
| Additional CPU / Core load from docker | 5W | measured compared to minimal system |
| PSU losses (~82% @ 4.4% load of 18.9W) | 4.2W | Cybenetics RM750e report + low-load penalty |
| Total | ~23W | observed gap of 10W compared to 33W wall measurement |
There is a gap of approximately 10-11W between the sum of the components and the observed power draw at the wall. Approximately 6W of that is directly related to the GSP firmware structure of the open drivers (reverting to an earlier version of the proprietary driver & disabling GSP brings the idle value down by 6W). This is a known issue with the open drivers and is unavoidable. The remainder of the gap (4-5W) is unaccounted for, but seems related to the GPU being present. Physically removing the card reduces power consumption to a level that is entirely explained by the items in the above table. This is apparently an issue with consumer RTX cards that the reported power usage in nvidia-smi only includes the GPU itself, and does not account for both the increase due to GSP firmware use, but also GDDR refreshing, VRMs or other components on the GPU.
The tl;dr here is that running the GPU on an up to date driver unavoidably consumes an additional 13-14W of power at idle, even though nvidia-smi is only reporting 3-4W of use. I've spent a LOT of effort trying to reduce this by having the card drop into D3Cold when idle, but since the motherboard does not support physically cutting the power rail to the GPU slot, this ends up consuming quite a bit MORE power than the current setup. See the gpu.md research log for more info.
The box has a few teaks in place to help optimize power consumption at idle:
sudo tee /etc/systemd/system/pmc-ltr-ignore.service <<EOF
[Unit]
Description=Ignore PMC LTR entries to allow deeper package C-states
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo 0 > /sys/kernel/debug/pmc_core/ltr_ignore"
ExecStart=/bin/sh -c "echo 4 > /sys/kernel/debug/pmc_core/ltr_ignore"
ExecStart=/bin/sh -c "echo 6 > /sys/kernel/debug/pmc_core/ltr_ignore"
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now pmc-ltr-ignore.service
sudo tee /etc/NetworkManager/dispatcher.d/99-eee <<EOF
#!/bin/bash
IFACE="$1"
ACTION="$2"
if [[ "$IFACE" == "enp5s0" && "$ACTION" == "up" ]]; then
ethtool --set-eee enp5s0 eee on
fi
EOF
sudo chmod +x /etc/NetworkManager/dispatcher.d/99-eee
sudo tee /etc/udev/rules.d/99-aura-autosuspend.rules <<EOF
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0b05", ATTR{idProduct}=="19af", ATTR{power/control}="auto"
EOF
power-profiles-daemon on all boots (default unit only wants graphical.target):
sudo systemctl enable power-profiles-daemon
sudo ln -sf /usr/lib/systemd/system/power-profiles-daemon.service \
/etc/systemd/system/multi-user.target.wants/power-profiles-daemon.service
The box is monitored by our home Grafana stack (TBD: I've got some nice dashboards for this that I should talk about)
sudo pacman -S prometheus-node-exportersudo systemctl enable --now prometheus-node-exporter/sys/class/powercap/ by default):
sudo mkdir -p /etc/systemd/system/prometheus-node-exporter.service.d
sudo tee /etc/systemd/system/prometheus-node-exporter.service.d/rapl.conf <<EOF
[Service]
AmbientCapabilities=cap_dac_read_search
EOF
sudo systemctl daemon-reload && sudo systemctl restart prometheus-node-exporter
sudo sensors-detect (accept all the defaults)17 commits
Python
71.2%
Shell
21.9%
ASL
6.9%
Rabble is our home server / gaming PC. My goal was to have a machine that could take over the burden of running the various things we run around the house, and also to act as a couch gaming PC with enough oomph to play any game we'd like. Some stuff about it:
This repo is a collection of notes around the planning, building, configuration and maintenance of the machine, mostly for my own records but also as a source if others want to build a similar setup.
I spent a little less than a month putting together the part list, focusing on a high-end-but-still-cost-conscious approach that I later found out mapped almost perfectly onto the Logical Increments 'outstanding' tier.
Not much to say about this; it's a pretty standard PC (despite the absurd case) and goes together without any real fuss. All of the parts other than the CPU cooler would port over to any standard SFF case if you're looking for something more conventional.
We designed the Nook print vinyl sticker & had it printed at StickerYou. The Tom Nook figurine is an Amiibo. We also bought a couple of light bars from AliExpress, and after some failed experiments trying to control them in software, I picked up a standalone RGB controller that is powered directly off the PSU and is stuck on the bottom of the case for the rare time that we actually want to have the RGB lighting on.
As this build is vulnerable to Intel's recent CPU self-destruct bug, the first thing we did at first boot was to update the BIOS to the latest version which is supposedly safe.
It's currently running the following BIOS settings (notes are somewhat terse but you can figure it out):
Many of these can optionally be managed in Linux, see the EFI Notes for details
sudo systemctl enable --now sshdssh-copy-id rabble.localrabble.local and:
echo 'PasswordAuthentication no' > /etc/ssh/sshd_config.d/99-local.confecho 'mat ALL=(ALL) NOPASSWD: ALL' > /etc/sudoers.d/99-localsudo pacman -S docker docker-composesudo systemctl enable --now dockersudo usermod -aG docker $USER (Log out and back in to get docker permissions)sudo pacman -S cachyos-gaming-meta steamsudo pacman -S neovim (pick the default providers for deps)chsh (select /bin/zsh)git clone git@github.com:mtrudel/dotfiles.gitcd dotfiles && ./install.zsh git nvim ssh zshWe set this up in a cabinet next to the TV (an LG OLED77B5PUA) and have it wired up like so:
sudo systemctl set-default multi-user.target2dc8:301c to 2dc8:310a). Handles two cases: bringing the whole session up from a bare console, or just waking an already-running session whose screen has gone DPMS-blank from idle
sudo tee /etc/udev/rules.d/99-8bitdo-tv.rules <<EOF
ACTION=="add", SUBSYSTEM=="hidraw", ATTRS{idVendor}=="2dc8", ATTRS{idProduct}=="310a", RUN+="/usr/bin/systemctl --no-block start 8bitdo-gui-wake.service"
EOF
sudo tee /etc/systemd/system/8bitdo-gui-wake.service <<EOF
[Unit]
Description=Switch to graphical session when 8BitDo controller connects
[Service]
Type=oneshot
ExecStart=/usr/bin/touch /run/switch-session-pending
ExecStart=/usr/bin/systemctl isolate graphical.target
ExecStart=-/usr/bin/runuser -u mat -- /bin/bash -c 'XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-0 kscreen-doctor --dpms on'
ExecStart=-/usr/local/bin/tv-ctl on
EOF
sudo systemctl daemon-reload && sudo udevadm control --reload-rules
The controller-wake and the PowerDevil idle-triggered auto-suspend (isolate multi-user.target, see below) can fire in opposite directions at nearly the
same instant. isolate stops everything not required by its destination
target so either one can kill the other as pure collateral. The touch
above is fast enough to complete before any kill, and
session-switch-resume.service below picks up the marker and finishes the
job once the race resolves.
sudo tee /etc/systemd/system/session-switch-resume.service <<EOF
[Unit]
Description=Resume to graphical.target after a manual session switch or a lost isolate race
ConditionPathExists=/run/switch-session-pending
After=multi-user.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'rm -f /run/switch-session-pending; systemctl isolate graphical.target'
ExecStart=-/usr/bin/runuser -u mat -- /bin/bash -c 'XDG_RUNTIME_DIR=/run/user/1000 WAYLAND_DISPLAY=wayland-0 kscreen-doctor --dpms on'
ExecStart=-/usr/local/bin/tv-ctl on
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable session-switch-resume.service
Screen dims after TurnOffDisplayIdleTimeoutSec (in ~/.config/powerdevilrc)
If the screen isn't blanking on schedule, systemctl --user restart plasma-powerdevil.service fixes it.systemd-suspend.service's ExecStart (wrapped in systemd-run --no-block since
isolating directly from within the unit that's running conflicts with itself):
sudo mkdir -p /etc/systemd/system/systemd-suspend.service.d
sudo tee /etc/systemd/system/systemd-suspend.service.d/99-drop-graphical-instead.conf <<EOF
[Service]
ExecStart=
ExecStart=/usr/bin/systemd-run --no-block --collect /usr/bin/systemctl isolate multi-user.target
EOF
sudo systemctl daemon-reload
tv-ctl over websocat/jq/wakeonlan to keep this simple and
dependency free:
sudo pacman -S websocat jq wakeonlansudo mkdir -p /etc/rabble-tv && sudo cp lgtv-manifest.json /etc/rabble-tv/manifest.json
(lgtv-manifest.json is the standard, publicly-reused LG
pairing manifest - the same com.lge.test blob bscpylgtv/lgtv2/etc. all use)sudo install -m 755 tv-ctl /usr/local/bin/tv-ctlsudo mkdir -p /var/lib/rabble-tv && sudo chown mat:mat /var/lib/rabble-tvtv-ctl register once (TV must be on; accept the prompt on-screen within 45s)
to populate /var/lib/rabble-tv/client-keyaudio/setVolume only updates the TV's own internal counter and does nothing
audible once sound is routed to an external soundbar over ARC/eARC - tv-ctl
instead tracks its own last-known level in /var/lib/rabble-tv/volume and
steps to the target via the same relative volumeUp/volumeDown calls the
physical remote's rocker useslogind broadcasts PrepareForSleep on any
suspend (including the fake one above) outside the systemd unit graph, so
NetworkManager briefly drops networking right when tv-ctl needs itgraphical.target:
sudo tee /etc/systemd/system/tv-gui-hook.service <<EOF
[Unit]
Description=Turn TV on/off in sync with entering/leaving the graphical session
[Service]
Type=oneshot
RemainAfterExit=yes
TimeoutStopSec=30
ExecStart=/usr/local/bin/tv-ctl on
ExecStop=/usr/local/bin/tv-ctl off
[Install]
WantedBy=graphical.target
EOF
sudo systemctl daemon-reload && sudo systemctl enable --now tv-gui-hook.service
(TimeoutStopSec=30 overrides this box's unusually short 10s default stop
timeout, giving tv-ctl's retries above room to actually finish)In low power mode (ie: when the display is not running and the GPU is idle), this setup pulls around 33-34W as measured at the wall. At peak gaming it can get up to 400W or more. I don't really care too much about power usage while gaming; that's a time-limited activity and basically par for the course. The far more relevant optimizations are around consumption when the box is idle.
I measured at ~33-34W at the wall with TV plugged in but off, ethernet connected, and USB devices as described below. Conditions: BIOS settings as above (notably RAM settings make a big difference), LTR ignore active, EEE enabled, all docker containers running:
| Component | Draw | Basis |
|---|---|---|
| CPU core (i5-13600K idle) | 3W | RAPL (measured via turbostat on minimal system) |
| CPU package (i5-13600K idle) | 0.5W | RAPL (measured via turbostat on minimal system) |
| GPU (RTX 4070, deep idle) | 3.4W | nvidia-smi (measured) |
| DDR5-4800 2×stick @ 1.1V | 3W | measured by booting with one stick removed |
| USB devices | 2W | measured with USB current meter |
| Fans (3× slow: 341/559/503 RPM) | 0.5W | estimated from spec sheets |
| NVMe SSD (P41 in APST PS4) | 0.5W | estimated from PS4 residency |
| Ethernet (I226-V + EEE) | 1W | measured on base system |
| Additional CPU / Core load from docker | 5W | measured compared to minimal system |
| PSU losses (~82% @ 4.4% load of 18.9W) | 4.2W | Cybenetics RM750e report + low-load penalty |
| Total | ~23W | observed gap of 10W compared to 33W wall measurement |
There is a gap of approximately 10-11W between the sum of the components and the observed power draw at the wall. Approximately 6W of that is directly related to the GSP firmware structure of the open drivers (reverting to an earlier version of the proprietary driver & disabling GSP brings the idle value down by 6W). This is a known issue with the open drivers and is unavoidable. The remainder of the gap (4-5W) is unaccounted for, but seems related to the GPU being present. Physically removing the card reduces power consumption to a level that is entirely explained by the items in the above table. This is apparently an issue with consumer RTX cards that the reported power usage in nvidia-smi only includes the GPU itself, and does not account for both the increase due to GSP firmware use, but also GDDR refreshing, VRMs or other components on the GPU.
The tl;dr here is that running the GPU on an up to date driver unavoidably consumes an additional 13-14W of power at idle, even though nvidia-smi is only reporting 3-4W of use. I've spent a LOT of effort trying to reduce this by having the card drop into D3Cold when idle, but since the motherboard does not support physically cutting the power rail to the GPU slot, this ends up consuming quite a bit MORE power than the current setup. See the gpu.md research log for more info.
The box has a few teaks in place to help optimize power consumption at idle:
sudo tee /etc/systemd/system/pmc-ltr-ignore.service <<EOF
[Unit]
Description=Ignore PMC LTR entries to allow deeper package C-states
After=local-fs.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c "echo 0 > /sys/kernel/debug/pmc_core/ltr_ignore"
ExecStart=/bin/sh -c "echo 4 > /sys/kernel/debug/pmc_core/ltr_ignore"
ExecStart=/bin/sh -c "echo 6 > /sys/kernel/debug/pmc_core/ltr_ignore"
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
sudo systemctl enable --now pmc-ltr-ignore.service
sudo tee /etc/NetworkManager/dispatcher.d/99-eee <<EOF
#!/bin/bash
IFACE="$1"
ACTION="$2"
if [[ "$IFACE" == "enp5s0" && "$ACTION" == "up" ]]; then
ethtool --set-eee enp5s0 eee on
fi
EOF
sudo chmod +x /etc/NetworkManager/dispatcher.d/99-eee
sudo tee /etc/udev/rules.d/99-aura-autosuspend.rules <<EOF
ACTION=="add", SUBSYSTEM=="usb", ATTR{idVendor}=="0b05", ATTR{idProduct}=="19af", ATTR{power/control}="auto"
EOF
power-profiles-daemon on all boots (default unit only wants graphical.target):
sudo systemctl enable power-profiles-daemon
sudo ln -sf /usr/lib/systemd/system/power-profiles-daemon.service \
/etc/systemd/system/multi-user.target.wants/power-profiles-daemon.service
The box is monitored by our home Grafana stack (TBD: I've got some nice dashboards for this that I should talk about)
sudo pacman -S prometheus-node-exportersudo systemctl enable --now prometheus-node-exporter/sys/class/powercap/ by default):
sudo mkdir -p /etc/systemd/system/prometheus-node-exporter.service.d
sudo tee /etc/systemd/system/prometheus-node-exporter.service.d/rapl.conf <<EOF
[Service]
AmbientCapabilities=cap_dac_read_search
EOF
sudo systemctl daemon-reload && sudo systemctl restart prometheus-node-exporter
sudo sensors-detect (accept all the defaults)17 commits
Python
71.2%
Shell
21.9%
ASL
6.9%