PiHelm is closed source and it asks for SSH credentials. That's a reasonable thing to be suspicious about, so here is the complete list of what it executes, generated from the source.
How it connects. Plain SSH, with a password or an RSA key in OpenSSH format. Ed25519 and ECDSA keys are not supported yet — the SSH library underneath only parses RSA. Nothing is installed on the Pi, no agent, no daemon, no cron job. There is no PiHelm server: your phone talks to your Pi and to nobody else. Credentials live in the iOS Keychain, and the host key is verified before any credential is sent.
Commands marked SUDO only run when you tap the action that needs them. The two marked SUDO -n are Health Score checks that try passwordless sudo and accept failure without prompting you. Everything else — every metric, every service check — is read-only and unelevated.
hostname | Name shown in the header |
uname -r | Kernel version |
cat /proc/device-tree/model | Which Raspberry Pi model, so thresholds match the hardware |
. /etc/os-release && echo "$PRETTY_NAME" | Distribution |
hostname -I | Local addresses |
whoami · nproc | User and core count |
uptime -p · cat /proc/uptime | Uptime, and the reboot guard for alerts |
tailscale ip -4 | Only to offer you the address for remote access. Fails silently if Tailscale isn't installed |
grep '^cpu' /proc/stat | CPU, total and per core |
top -bn1 | grep 'Cpu(s)' | CPU for the quick background sample |
free -m | Memory and swap |
df -BM / · df -P / | Disk usage |
cat /proc/loadavg | Load average |
cat /sys/class/thermal/thermal_zone0/temp | Temperature |
vcgencmd measure_temp · vcgencmd get_throttled | GPU temperature and throttling / undervoltage flags |
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq | Clock speed |
cat /proc/net/dev | Network throughput |
grep -E ' (mmcblk0|sda|nvme0n1|vda) ' /proc/diskstats | Disk I/O |
findmnt -no SOURCE / | Whether root is on SD, USB or NVMe |
systemctl list-unit-files --type=service | What's installed, and whether it's enabled at boot |
systemctl list-units --all --type=service | What's loaded and what state it's in |
docker ps -a --format '{{.Names}}|{{.Image}}|{{.Status}}|{{.Ports}}' | Containers, images, status and published ports |
docker inspect --format '{{.Name}}|{{.HostConfig.RestartPolicy.Name}}|{{.State.OOMKilled}}|{{.State.ExitCode}}|{{.State.FinishedAt}}' $ids | Restart policy and exit code, to tell a deliberate stop from a crash |
ps -eo comm= · ss -ltn · ss -lun · cat /proc/uptime | Corroborating signals. Never used alone to identify a service |
Every probe is curl -s -o /dev/null -w '%{http_code}' --connect-timeout 1 --max-time 2 against the address the container actually publishes, chosen from its port mapping rather than assumed:
Home Assistant → :8123/ | HTTP status only |
Plex → :32400/identity | Unauthenticated by design |
Jellyfin → :8096/System/Info/Public | Public endpoint |
Node-RED → :1880/settings | Answers 200 or 401; either proves it's alive |
OctoPrint → :5000/api/version | No API key needed for version |
Frigate → :5000/api/version | Internal port, unauthenticated |
Syncthing → :8384/rest/noauth/health | Explicitly the no-auth endpoint |
Grafana → :3000/api/health | Unauthenticated by design |
Transmission → :9091/transmission/rpc | The 409 handshake is proof of life |
Pi-hole, AdGuard Home, Mosquitto, Zigbee2MQTT, Portainer and Uptime Kuma have no endpoint that answers without credentials, so PiHelm does not probe them and says running, unverified instead of pretending to know.
docker start|stop|restart NAME | Container buttons |
docker logs --tail 50 NAME | The Logs button |
docker pull IMAGE | Pulls a newer image. It does not recreate the running container — that one keeps its old image until you recreate it yourself |
docker rm -f NAME | Remove, behind a confirmation |
systemctl start|stop|restart UNITSUDO | systemd controls |
journalctl -u UNIT --no-pager -n N | Unit logs |
ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu|-%mem --no-headers | head -25 | Process list, sorted by whichever column you picked |
kill -SIGNAL PIDSUDO | Killing a process, behind a confirmation. Elevated because the list includes processes you don't own |
ip -o addr · ip -o link · ip route get 8.8.8.8 · ss -tunlH | The network screen |
ping -c 4 -W 2 HOST | Network tools · ping |
traceroute -n -w 2 -m 15 HOST | Network tools · traceroute |
dig +short A|AAAA|MX HOST, falling back to host HOST then getent hosts HOST | Network tools · DNS, whichever of the three exists |
arp-scan --localnetSUDO, falling back to ip neigh show then arp -a | Network tools · neighbours. The sudo attempt is sudo -n and failure is expected |
for p in 22 80 443 53 25 110 143 3306 5432 6379 8080 8443 9000 27017; do timeout 1 bash -c "echo > /dev/tcp/HOST/$p"; done | Network tools · common-port check against a host you type |
| Whatever you typed | The terminal and your own saved commands run exactly what you wrote, nothing added |
sudo -n and accept failure rather than asking you for anything.getent passwd pi | Whether the default account still exists |
sudo -n grep PasswordAuthentication /etc/ssh/sshd_configSUDO -n | Whether SSH still accepts passwords. On stock Raspberry Pi OS that file is world-readable and sudo is unnecessary; the elevation is there for systems that lock it down, and it gives up quietly |
sudo -n trueSUDO -n | Tests whether sudo needs a password at all |
stat -c %Y /var/cache/apt/pkgcache.bin | How long since the last apt update |
If you saved a sudo password, it is piped to sudo -S for that one command and never written anywhere on the Pi. If you didn't, PiHelm tries sudo -n, which only works when your user already has passwordless sudo, and gives up cleanly when it doesn't. It never edits sudoers, never installs a helper, and never keeps a session open.