Chillbox 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, read out of the source by hand and checked against it again for 1.5.0.
How it connects. Plain SSH, with a password or a private key in the OpenSSH container. Ed25519 is the one to use, with or without a passphrase. RSA is accepted with a warning: the SSH library underneath signs it with SHA-1 and OpenSSH 8.8 and later disable ssh-rsa by default, so whether the server takes it is the server's call. ECDSA can't be parsed at all, and a PEM/PKCS#8 file or a PuTTY .ppk is refused at import, with the way to convert it. Nothing is installed on the server, no agent, no daemon, no cron job. There is no Chillbox server: your phone talks to your server over SSH and to no other machine. Keys and passwords live in the iOS Keychain, on the device unless you switch a key over to iCloud Keychain yourself, and the host key is verified before any credential is sent.
Everything below runs over one SSH connection at a time, capped at four channels open at once, with the terminal and the file browser counted in the same four, and each one is closed when the command it was opened for finishes.
Commands marked SUDO only run when you tap the action that needs them. Ones marked SUDO -n try passwordless sudo and give up quietly instead of prompting you for anything: one in the assessment, three more in the assessment's privileged checks, which stay off until you switch them on in Settings, one in Network tools' neighbours scan, and one in a sudo diagnostics tool tucked into the Systemd screen. Everything else — every metric, every service check — is read-only and unelevated.
Several of these differ on macOS, because the Linux command does not exist there. Chillbox picks from the platform check below, rather than running a command it knows will fail:
hostname, or scutil --get ComputerName on macOS | Name shown in the header |
uname -r | Kernel version. Same command on both |
cat /proc/device-tree/model | tr -d '\0' | Device-tree model string. On a Raspberry Pi (or another SBC that exposes one) this names the exact hardware; most VPS and NAS kernels don't have this file at all, and the read just comes back empty. Not run on a Mac, which has no equivalent. The model line there is built from sw_vers below |
. /etc/os-release && echo "$PRETTY_NAME" | Distribution. Not run on a Mac, which has no /etc/os-release |
hostname -I, or on macOS ipconfig getifaddr $(route -n get default | awk '/interface:/{print $2}') | Local addresses. BSD hostname has no -I, so on a Mac it's the default route's interface address instead |
sw_vers -productVersion | macOS version. Empty on Linux, which is how the app knows it isn't looking at a Mac |
tailscale ip -4 | head -1 | Only to offer you the address for remote access. Fails silently if Tailscale isn't installed |
First a single uname -s, on its own, to find out whether this is Linux or macOS. Then a one-shot batch, a different script per platform, that fingerprints the host, so the commands below know what to expect instead of assuming a Raspberry Pi.
On Linux:
uname -s · uname -m | Kernel name and CPU architecture |
( . /etc/os-release && echo "$ID" ) | Distribution ID, used to pick sensible defaults for the checks below |
command -v apt|dnf|pacman|apk|zypper | Which package manager is installed, tried in that order — feeds the update-staleness check in the assessment |
Every readable /sys/class/thermal/thermal_zone*/type, hwmon*/name and hwmon*/temp*_input | Every temperature source the host exposes, so Chillbox can pick whichever one is actually the CPU sensor instead of assuming a fixed path |
awk over /proc/self/mountinfo, then cat /proc/diskstats | Resolves which diskstats row is the root filesystem's device, from the mount table rather than a guessed list of device names |
cat /proc/device-tree/model | Same read as above, this time just to decide whether the host is a Raspberry Pi — gates the vcgencmd extras further down |
grep -c ^processor /proc/cpuinfo | Core count |
Readability check on /proc/stat, /proc/meminfo, /proc/uptime, /proc/net/dev | Confirms the universal /proc files the CPU, memory and network cards depend on are actually there, so a missing one shows as absent instead of a fabricated zero |
On macOS, where none of the above exists, the whole batch is four commands:
uname -srm | Kernel name, release and architecture |
sw_vers | macOS product name, version and build |
sysctl -n hw.ncpu | Core count |
sysctl -n hw.memsize | Total memory, which vm_stat below reports in pages and not bytes |
On Linux:
cat /proc/stat | CPU, total and per core |
cat /proc/meminfo | Memory and swap |
cat /proc/uptime | Uptime |
df -P -k / | Disk usage |
cat /proc/net/dev | Network throughput |
cat /proc/loadavg | Load average |
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq | Clock speed |
| Read of whichever thermal path was found above | Temperature — a thermal-zone or hwmon path picked by the platform check, not a hardcoded one. Absent entirely on a host with no readable sensor |
awk '$3=="<device>"' /proc/diskstats | Disk I/O, for the root device the platform check resolved |
ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu --no-headers | head -5 | Top 5 processes by CPU, for the dashboard's quick preview card |
vcgencmd get_throttled · vcgencmd measure_temp | GPU temperature and throttling / undervoltage flags — only when the platform check found a Raspberry Pi |
On macOS it is a different batch, for the same cards:
top -l 2 -n 0 -s 1 | CPU. Two samples, because the first line top prints is an average since boot, not a current rate |
vm_stat | Memory |
sysctl -n vm.loadavg | Load average |
sysctl -n kern.boottime | Uptime |
df -P -k /System/Volumes/Data, falling back to df -P -k / | Disk usage, on the Data volume where a Mac's files actually live |
netstat -ib | Network throughput |
route -n get default | Which interface the throughput numbers should come from |
ps -Aco pid,user,%cpu,%mem,comm -r | head -6 | Top processes for the dashboard's preview card. BSD ps has no --no-headers, so it's six lines for five rows |
There is no temperature, no disk I/O and no vcgencmd in the macOS batch. macOS does not expose a CPU temperature over SSH without root, and the app says so rather than leaving a blank that reads like a broken sensor.
When a background check runs with the app closed, this whole batch runs once, and on Linux /proc/stat is read twice, a second apart, so a CPU rate can be computed without a previous sample to compare against.
The systemctl and docker calls carry a timeout 5, and each is preceded by a test ([ -d /run/systemd/system ], command -v docker, command -v ss, command -v curl), so a host without one of them is reported as not having it, never as having nothing:
systemctl list-unit-files --type=service --no-legend --plain | What's installed, and whether it's enabled at boot. Skipped on a host with no systemd |
systemctl list-units --all --type=service --no-legend --plain | What's loaded and what state it's in |
docker ps -a --format '{{.Names}}|{{.Image}}|{{.Status}}|{{.Ports}}' | Containers, images, status and published ports. Skipped when Docker isn't installed |
ids=$(timeout 5 docker ps -aq) | Collects the container IDs the next command inspects |
docker inspect --format '{{.Name}}|{{.HostConfig.RestartPolicy.Name}}|{{.State.OOMKilled}}|{{.State.ExitCode}}|{{.State.FinishedAt}}|{{.RestartCount}}|{{.State.StartedAt}}' $ids | Restart policy, exit code and restart count, to tell a deliberate stop from a crash and a crash from a restart loop |
ps -eo comm= | sort -u · ss -ltn · ss -lun · cat /proc/uptime | Corroborating signals and the reboot guard for alerts. Never used alone to identify a service |
On a Mac the Docker half of the batch is unchanged and the systemd half reports itself absent. Two more commands are added:
for c in brew /opt/homebrew/bin/brew /usr/local/bin/brew, then "$BIN" services list | What Homebrew manages, and what it declares each one's status to be. The three candidates exist because a non-interactive SSH shell often doesn't have Homebrew on its PATH |
launchctl list "homebrew.mxcl.NAME" | grep '"PID"', once per formula | Whether it is actually running. brew services list's own status column is what Homebrew was told to do, not what launchd is doing. A bare launchctl list returns hundreds of Apple entries, so it is asked one label at a time |
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 Chillbox does not probe them and says running, unverified instead of pretending to know.
docker ps -a -s --format '{{.Names}}\t{{.Status}}\t{{.Image}}\t{{.Size}}\t{{.Ports}}' | The Docker screen's own list, including image size |
docker start|stop|restart NAME | Container buttons |
docker logs --tail 50 NAME 2>&1 | The Logs button. Merged, because containers write to both streams and splitting them loses the order |
docker inspect NAME | The Inspect sheet, full raw output |
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 list-units --type=service --all --no-legend --no-pager --plain | Refreshes the full unit list when you open the Systemd screen or pull to refresh — separate from the batch that drives the service cards |
systemctl start|stop|restart UNITSUDO | systemd controls |
journalctl -u UNIT --no-pager -n N | Unit logs |
whoami · sudo -n true 2>&1; echo EXIT=$? · whoami again through the sudo pathSUDO -n | A sudo diagnostics tool tucked into the Systemd screen. Shows exactly what each auth path returns |
brew services start|stop|restart FORMULA, after a for c in brew /opt/homebrew/bin/brew /usr/local/bin/brew cascade to find the binary | The Homebrew service buttons on a Mac. Not elevated. brew services runs as your own user |
top -bn2 -d 0.5 -o %CPU|%MEM -w 256 | awk '/^top -/{n++} n==2 && $1 ~ /^[0-9]+$/ {print $1,$2,$9,$10,$12}' | head -25, falling back to ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu|-%mem --no-headers | head -25 | Process list on Linux. Two top samples give real-time %CPU; the ps fallback only kicks in when top isn't available (BusyBox, minimal images), and its %CPU is cumulative rather than live |
ps -Aco pid,user,%cpu,%mem,comm -r|-m | head -25 | Process list on macOS. BSD top has no batch mode and BSD ps has no --sort, so it's ps's own -r / -m sort flags instead |
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 · ss -tunlH | head -50 | The network screen, on Linux |
ifconfig -a · netstat -an -p tcp · netstat -an -p udp | The network screen, on macOS |
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 |
If arp-scan exists: sudo -n arp-scan --localnetSUDO -n, falling back to arp -a. If it doesn't: ip neigh show, falling back to 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: timeout 1 bash -c "echo > /dev/tcp/HOST/$p" | 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 |
This one runs no shell commands at all. It opens SSH's own SFTP subsystem, one channel, opened once when the screen appears and closed when it goes away, never one per listing, and uses the protocol's own operations: realpath, opendir, stat, open/read/write/close, mkdir, rename, remove, rmdir. Nothing is spawned on the server and nothing is piped through a shell.
Reads and writes go in 32 KB chunks, straight between the socket and a file on the phone, so the memory a transfer needs does not depend on the size of the file. An upload is written to a temporary name in the destination folder and renamed onto the real one only after the last byte lands; if it fails or you cancel it, the staged file is removed and what was already on the server is untouched. Deleting requires a switch that is off every time the screen opens, and then a confirmation naming the file. A folder is removed with rmdir, which means only when it is already empty. There is no recursive delete.
The assessment reads the numbers the Metrics batch above already collected. It does not take a second sample of CPU, memory, disk or temperature. What it does issue on its own is this:
vcgencmd get_throttled | Under-voltage and throttling flags. Raspberry Pi only, and a second, independent read of the same command the Metrics batch already made |
findmnt -no SOURCE / | Whether root is on an SD card. The resulting advice only surfaces on a Raspberry Pi; other hosts booting off eMMC or similar just fall through with no finding |
getent passwd pi | Whether the default account still exists. Raspberry Pi only — "pi" means nothing on any other distro |
sudo -n grep -E '^\s*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 |
ip route get 8.8.8.8, or on macOS route -n get default then networksetup -getairportnetwork IFACE | Whether the primary route goes out over WiFi, less reliable than Ethernet for a server left running 24/7. A Mac has no wlan* naming convention to read, so the interface has to be asked directly whether it is Wi-Fi hardware |
stat -c %Y <package-cache-path> | How long since the last package index refresh. The path depends on whichever package manager the platform check found: apt's cache file, dnf's or zypper's cache directory, pacman's sync directory, apk's cache |
stat -f %m /opt/homebrew/.git/FETCH_HEAD or /usr/local/Homebrew/.git/FETCH_HEAD · stat -f %m ~/Library/Caches/Homebrew/api/formula.jws.json | The same question on macOS. A timestamp, never brew outdated, which takes seconds and would make the monitored host reach out to the internet. Which of the two files exists depends on the Homebrew version, so both are read and the newer wins |
cat /proc/uptime | Flags an uptime over 90 days as a reboot you're probably overdue for, after kernel updates |
date +%s | The server's clock, compared against the phone's at both ends of the round trip. First in the batch on purpose: further down, the batch's own runtime would start being reported as clock drift |
df -i -P / | Inode usage. A filesystem can be out of inodes with the disk half empty, and nothing else on the dashboard would show it |
awk '{print "UPTIME:" $1}' /proc/uptime, then per unit systemctl show UNIT -p NRestarts -p ActiveEnterTimestampMonotonic --value | Restart loops. Only for units the service pass already identified, never a scan of everything. The uptime read is the reboot guard: after a reboot the counters are meaningless |
For each of 443 8443 9443 8006 636 993 995 the service pass found something listening on: echo | timeout 3 openssl s_client -connect ADDR:PORT, then openssl x509 -noout -checkend 259200 and -checkend 1209600 | Certificates expiring in 3 or 14 days. The verdict comes from the text openssl prints, not its exit code. -checkend returns the same code for "expires soon" and "there was no certificate here", and reading the code would turn the first probe of a plain-HTTP port into a false alarm |
The rest of that batch only runs when you switch on privileged checks in Settings, which is off by default. With it off, these are not in the script at all, not even as a section that comes back empty:
sudo -n smartctl -H -A -n standby /dev/DEVICESUDO -n | Disk health. -n standby so a sleeping mechanical disk is never woken up just because the assessment ran. Skipped entirely when the root device is unknown or is an SD card, which has no SMART to report |
journalctl -u ssh -u sshd --since -24h | grep -cE 'Failed password|Invalid user', then the same through sudo -n, then /var/log/auth.log and /var/log/messages, then those through sudo -nSUDO -n | Failed SSH logins in the last 24 hours. Four rungs, unprivileged first, and each one tests that it can read before it counts. A log it cannot read has to come back as unreadable, not as zero failures |
log show --predicate 'process == "sshd"' --last 24h --style compact | grep -icE 'Failed|invalid' | The same count on macOS, which has neither journalctl nor auth.log |
sudo -n sshd -T | grep -i '^passwordauthentication 'SUDO -n | Whether password login is actually in effect, which is not always what /etc/ssh/sshd_config says. A drop-in file can override it |
If you saved a sudo password, it is piped to sudo -S for that one command and never written anywhere on the server. If you didn't, Chillbox 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.