Steam Deck Setup
This tutorial takes you from a fresh SteamOS install to a fully working protoPen installation with the server running as a systemd user service. By the end, protoPen will start automatically on boot and be reachable over Tailscale SSH.
Time: ~30 minutes
Prerequisites:
- Steam Deck with a fresh SteamOS install, connected to your network
- A workstation on the same network (for SSH access)
- An Infisical account with access to the protoPen project secrets
1. Enable SSH
SteamOS ships with no password and sshd disabled. You need to fix both.
On the Steam Deck — switch to Desktop Mode, open Konsole, and run:
# Set a password for the deck user
passwd
# Enable and start the SSH daemon
sudo systemctl enable sshd
sudo systemctl start sshd
# Grab your IP for the next step
hostname -IFrom your workstation, verify you can connect:
ssh deck@<DECK_IP>Key-based auth
Set up SSH keys now so you never need the password again:
ssh-keygen -t ed25519 # skip if you already have a key
ssh-copy-id deck@<DECK_IP>Once Tailscale is installed (later in this guide), you will connect via:
ssh deck@steamdeck2. Disable the read-only filesystem
SteamOS mounts root as read-only. We need to disable this and configure passwordless sudo for remote commands.
# Disable the read-only lock
sudo steamos-readonly disable
# Set up passwordless sudo
echo 'deck ALL=(ALL) NOPASSWD: ALL' | sudo tee /etc/sudoers.d/zz-deck
sudo chmod 0440 /etc/sudoers.d/zz-deck
# Initialize the pacman keyring
sudo pacman-key --init
sudo pacman-key --populate archlinuxSteamOS updates
SteamOS updates may re-enable the read-only lock and wipe /etc/sudoers.d/zz-deck. If pacman or passwordless sudo stops working after an update, re-run the commands above.
Why zz-deck? SteamOS has /etc/sudoers.d/wheel which requires a password. Sudoers drop-in files load alphabetically — zz- ensures the NOPASSWD rule wins.
3. Install BlackArch repository
BlackArch layers ~2800 pentest tools on top of SteamOS via pacman without replacing the OS.
# Download and run the strap script
curl -fsSL https://blackarch.org/strap.sh -o /tmp/strap.sh
# Verify the SHA1 hash (check https://blackarch.org/downloads.html for the current value)
sha1sum /tmp/strap.sh
# Install
chmod +x /tmp/strap.sh
sudo /tmp/strap.shInstall the tools protoPen uses:
sudo pacman -Sy --noconfirm \
nmap \
aircrack-ng \
bettercap \
wireshark-cliVerify everything installed:
nmap --version
bettercap -version
tshark --version | head -1TIP
tshark is part of the wireshark-cli package on Arch.
4. Install Python, Git, and GitHub CLI
SteamOS Holo ships with Python 3.13 and git pre-installed. Verify:
python --version # needs 3.12+
git --versionIf Python is missing or too old:
sudo pacman -Sy --noconfirm python python-pip git base-develInstall GitHub CLI and authenticate:
sudo pacman -Sy --noconfirm github-cli
gh auth login
gh auth setup-gitWARNING
You must run gh auth setup-git — without it, git clone over HTTPS will fail with "could not read Username" since there is no interactive TTY in remote SSH sessions.
5. Install Tailscale
Tailscale provides the stable SSH connection between your workstation and the Deck, regardless of what WiFi network either device is on.
sudo pacman -Sy --noconfirm tailscale
sudo systemctl enable tailscaled
sudo systemctl start tailscaled
sudo tailscale up --sshFollow the auth URL printed to the terminal. Once authenticated, you can reach the Deck from any device on your tailnet:
ssh deck@steamdeck6. Clone and install protoPen
git clone https://github.com/protoLabsAI/protoPen.git ~/protoPen
cd ~/protoPen
git submodule update --init --recursive
# Create a virtual environment
python -m venv .venv
source .venv/bin/activate
# Install dependencies
pip install --upgrade pip
pip install -r requirements.txt
# Install the nanobot submodule
pip install ./nanobot/Build errors on sqlite-vec
If pip install fails on sqlite-vec, install the build toolchain first:
sudo pacman -S --noconfirm gcc cmakeRun the test suite to verify the install:
cd ~/protoPen
source .venv/bin/activate
python -m pytest tests/ -vAll 196 tests should pass.
7. Install Infisical CLI and configure service token
protoPen fetches secrets at runtime from Infisical — no .env files on disk. Install the CLI:
curl -1sLf 'https://artifacts.infisical.com/repos/infisical/setup.rpm.sh' | sudo -E bash
sudo pacman -Sy --noconfirm infisicalCreate a service token
start.sh uses an INFISICAL_TOKEN service token for non-interactive auth (no infisical login required). Create a token in the Infisical dashboard for the protoPen project (f7d3c43d-be5e-4a05-ac4c-c69d1e09d6c7), scoped to the prod environment.
Inject the token via systemd override
Store the token in a systemd drop-in so it is available at boot without touching disk in the repo:
mkdir -p ~/.config/systemd/user/protopen.service.d
cat > ~/.config/systemd/user/protopen.service.d/infisical.conf << 'EOF'
[Service]
Environment=INFISICAL_TOKEN=<your-service-token>
EOF
systemctl --user daemon-reloadVerify secrets fetch
INFISICAL_TOKEN=<your-service-token> infisical export \
--domain https://secrets.proto-labs.ai/api \
--env prod \
--format dotenv \
--silent \
--token "$INFISICAL_TOKEN"You should see all project secrets (including LITELLM_MASTER_KEY, ANTHROPIC_API_KEY, etc.). start.sh exports all Infisical secrets into the process environment automatically — no need to cherry-pick individual keys.
Zero env-on-disk policy
protoPen never writes API keys or secrets to disk. The start.sh launcher fetches secrets from Infisical into environment variables at boot and they exist only in process memory. The only exception is the INFISICAL_TOKEN in the systemd override, which lives outside the repo at ~/.config/systemd/user/protopen.service.d/infisical.conf.
8. Create the /sandbox symlink
protoPen stores knowledge, audit logs, and papers under /sandbox. On bare metal (no Docker), this needs to be a symlink to a local data directory:
mkdir -p ~/protoPen/data/knowledge ~/protoPen/data/audit ~/protoPen/data/intel ~/protoPen/data/lab
sudo ln -sfn ~/protoPen/data /sandbox9. Create a systemd user service
A systemd user service starts protoPen automatically on boot without requiring a login session.
Create the service file:
mkdir -p ~/.config/systemd/user
cat > ~/.config/systemd/user/protopen.service << 'EOF'
[Unit]
Description=protoPen — autonomous pen-testing agent
After=network-online.target tailscaled.service
Wants=network-online.target
[Service]
Type=simple
WorkingDirectory=/home/deck/protoPen
ExecStart=/home/deck/protoPen/start.sh --port 7870
Restart=on-failure
RestartSec=10
Environment=AGENT_BACKEND=langgraph
[Install]
WantedBy=default.target
EOFEnable and start it:
systemctl --user daemon-reload
systemctl --user enable protopen.service
systemctl --user start protopen.serviceCheck that it is running:
systemctl --user status protopen.service10. Enable linger
By default, systemd user services only run while the user has an active login session. Linger keeps them running after you disconnect SSH:
sudo loginctl enable-linger deck11. Set Desktop Mode as default boot target
protoPen runs headless — you do not need Game Mode. Setting Desktop Mode as the default avoids wasting resources on the Steam client:
sudo steamos-session-select plasma-persistentThe Deck will now boot directly into KDE Plasma desktop.
12. Verify the server
From your workstation, check that the server is reachable:
# Health check — should return the agent card JSON
curl -s http://steamdeck:7870/.well-known/agent.json | head -20
# Quick chat test
curl -s http://steamdeck:7870/api/chat \
-H "Content-Type: application/json" \
-d '{"message": "/help"}' | python -m json.toolYou can also open the Gradio UI in a browser at http://steamdeck:7870.
Checking logs
If the server is not responding, check the journal:
journalctl --user -u protopen.service -fTroubleshooting
| Issue | Fix |
|---|---|
pacman fails with read-only errors | sudo steamos-readonly disable |
sudo still asks for password | Verify file is named zz-deck with 0440 perms: ls -la /etc/sudoers.d/zz-deck |
| BlackArch strap fails GPG check | sudo pacman-key --refresh-keys |
git clone fails "could not read Username" | Run gh auth setup-git to wire the credential helper |
pip install fails on sqlite-vec | Install build deps: sudo pacman -S gcc cmake |
| SteamOS update broke everything | Re-run steps 2 and 3 (read-only disable, sudoers, pacman keyring) |
| Infisical fetch returns empty | Verify INFISICAL_TOKEN is set in the systemd override and run systemctl --user daemon-reload |
| Server not reachable over Tailscale | Verify sudo tailscale up --ssh and check tailscale status |
| tshark/dumpcap "permission denied" | sudo usermod -aG wireshark deck then restart the protopen service |
tool_use ids without tool_result errors | Corrupted session DB — rm -f /sandbox/knowledge/sessions.db* and restart |
Post-install: enable packet capture
tshark (Wireshark CLI) requires the wireshark group for raw packet capture:
sudo usermod -aG wireshark deckVerify after re-login or service restart:
groups deck # should include 'wireshark'This enables the blackarch tshark_capture and net_monitor traffic_baseline tools for live LAN traffic analysis.
Hardware: HackRF / PortaPack
The PortaPack H4M (HackRF One + Mayhem firmware) requires extra setup on SteamOS because:
- Wrong USB product ID in stock libhackrf — Mayhem enumerates as
1d50:6018. Stock libhackrf only recognises1d50:6089(HackRF One) and silently finds nothing. - No udev rule for
0x6018— the packaged udev rules don't cover Mayhem's product ID, so the device node is not accessible without root.
Power requirement
The PortaPack draws ~500mA. The official Steam Deck dock allocates 160–500mA per USB-A port and the device may fail to enumerate through it. Connect directly to the Deck's USB-C port, or use an externally powered hub.
1. Verify enumeration
lsusb -v -d 1d50:6018 | grep -E 'iManufacturer|iProduct'Expected output:
iManufacturer 1 Great Scott Gadgets
iProduct 2 PortaPack MayhemIf no output, check the cable (must be a data cable, not charge-only) and the USB-C port.
2. Install the hackrf package and udev rule
sudo pacman -S --noconfirm hackrf soapyhackrf
echo 'ATTR{idVendor}=="1d50", ATTR{idProduct}=="6018", SYMLINK+="hackrf-portapack-%k", TAG+="uaccess"' | \
sudo tee /etc/udev/rules.d/53-hackrf-portapack.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --attr-match=idVendor=1d503. Patch and rebuild libhackrf
Stock libhackrf (2024.02.1-3) does not recognise 0x6018. Build a patched version from source.
Set up an Arch distrobox (required because SteamOS rootfs lacks glibc dev headers):
distrobox create --name archbuild --image archlinux:latest --no-entry
distrobox enter archbuild -- sudo pacman -S --noconfirm base-devel cmake libusbClone and patch hackrf:
git clone --depth=1 https://github.com/greatscottgadgets/hackrf.git ~/hackrf-portapack-srcEdit ~/hackrf-portapack-src/host/libhackrf/src/hackrf.c:
After the
rad1o_usb_pidline, add:cstatic const uint16_t portapack_mayhem_usb_pid = 0x6018;In the
hackrf_device_list()product-ID check (around line 584), addportapack_mayhem_usb_pidto both OR-chains that filter byidProduct. There are two identical blocks — search forrad1o_usb_pidand add the new constant after each occurrence:c(device_descriptor.idProduct == rad1o_usb_pid) || (device_descriptor.idProduct == portapack_mayhem_usb_pid))
Build and install:
distrobox enter archbuild -- bash -c "
cd ~/hackrf-portapack-src/host
cmake -B build2 -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Release
cmake --build build2 -j4
"
sudo cmake --install ~/hackrf-portapack-src/host/build2
sudo ldconfigVerify:
hackrf_infoExpected:
Found HackRF
Index: 0
Serial number: Transceiver
hackrf_board_id_read() failed: Pipe error (-1000)Found HackRF is success. The Pipe error on board_id_read is normal — Mayhem intercepts that control transfer. SDR software via SoapyHackRF works correctly:
SoapySDRUtil --find='driver=hackrf'
# → Found device 0 driver=hackrf serial=00000030...4. Persist through OS updates
SteamOS A/B updates will overwrite /usr/lib/libhackrf.so*. Add the patched files to the keep list:
sudo tee -a /etc/atomic-update.conf.d/protopen-keep.conf << 'EOF'
/etc/udev/rules.d/53-hackrf-portapack.rules
/usr/lib/libhackrf.so.0.10.0
/usr/lib/libhackrf.so.0
/usr/lib/libhackrf.so
/usr/bin/hackrf_info
EOFAfter any SteamOS OS update, re-run the reinstall script:
~/hackrf-portapack-src/reinstall.shTroubleshooting
| Symptom | Fix |
|---|---|
lsusb shows no 1d50: device | Cable is charge-only; try a different cable or port. Device not powered. |
lsusb shows 1d50:6018 but hackrf_info says "No HackRF boards found" | Patched libhackrf not installed — run step 3. |
hackrf_info exits with Pipe error only | Normal — Mayhem firmware. Enumeration succeeded. |
SoapySDRUtil --find returns nothing | Run step 3 first; SoapyHackRF uses libhackrf internally. |
After OS update, hackrf_info reverts to "No HackRF boards found" | Run ~/hackrf-portapack-src/reinstall.sh. |
distrobox build fails — stdint.h: No such file or directory | Copy from a container: `find ~/.local/share/containers -name 'stdint.h' -not -path '/isl/' -not -path '/c++/' |
What's next
With protoPen running on the Deck, continue to the First Engagement tutorial to run a passive network scan using only the Deck's built-in hardware.