Every command Chillbox runs on your server

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.

Identifying the machineOnce, when you connect.

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 macOSName shown in the header
uname -rKernel 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 -productVersionmacOS version. Empty on Linux, which is how the app knows it isn't looking at a Mac
tailscale ip -4 | head -1Only to offer you the address for remote access. Fails silently if Tailscale isn't installed

Working out what kind of host it isOnce per connection, alongside the commands above.

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 -mKernel 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|zypperWhich 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*_inputEvery 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/diskstatsResolves 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/modelSame 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/cpuinfoCore count
Readability check on /proc/stat, /proc/meminfo, /proc/uptime, /proc/net/devConfirms 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 -srmKernel name, release and architecture
sw_versmacOS product name, version and build
sysctl -n hw.ncpuCore count
sysctl -n hw.memsizeTotal memory, which vm_stat below reports in pages and not bytes

MetricsOn the refresh interval you choose, from 3 to 30 seconds.

On Linux:

cat /proc/statCPU, total and per core
cat /proc/meminfoMemory and swap
cat /proc/uptimeUptime
df -P -k /Disk usage
cat /proc/net/devNetwork throughput
cat /proc/loadavgLoad average
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freqClock speed
Read of whichever thermal path was found aboveTemperature — 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/diskstatsDisk I/O, for the root device the platform check resolved
ps -eo pid,user,%cpu,%mem,comm --sort=-%cpu --no-headers | head -5Top 5 processes by CPU, for the dashboard's quick preview card
vcgencmd get_throttled · vcgencmd measure_tempGPU 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 1CPU. Two samples, because the first line top prints is an average since boot, not a current rate
vm_statMemory
sysctl -n vm.loadavgLoad average
sysctl -n kern.boottimeUptime
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 -ibNetwork throughput
route -n get defaultWhich interface the throughput numbers should come from
ps -Aco pid,user,%cpu,%mem,comm -r | head -6Top 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.

Finding your servicesA full batch on connect, and again every 20 seconds.

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 --plainWhat's installed, and whether it's enabled at boot. Skipped on a host with no systemd
systemctl list-units --all --type=service --no-legend --plainWhat'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}}' $idsRestart 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/uptimeCorroborating 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 listWhat 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 formulaWhether 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

The liveness probescurl, from the server to its own loopback, with a 2-second cap. Only endpoints that need no credentials, run in parallel.

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/identityUnauthenticated by design
Jellyfin → :8096/System/Info/PublicPublic endpoint
Node-RED → :1880/settingsAnswers 200 or 401; either proves it's alive
OctoPrint → :5000/api/versionNo API key needed for version
Frigate → :5000/api/versionInternal port, unauthenticated
Syncthing → :8384/rest/noauth/healthExplicitly the no-auth endpoint
Grafana → :3000/api/healthUnauthenticated by design
Transmission → :9091/transmission/rpcThe 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.

Only when you tap something

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 NAMEContainer buttons
docker logs --tail 50 NAME 2>&1The Logs button. Merged, because containers write to both streams and splitting them loses the order
docker inspect NAMEThe Inspect sheet, full raw output
docker pull IMAGEPulls a newer image. It does not recreate the running container — that one keeps its old image until you recreate it yourself
docker rm -f NAMERemove, behind a confirmation
systemctl list-units --type=service --all --no-legend --no-pager --plainRefreshes 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 UNITSUDOsystemd controls
journalctl -u UNIT --no-pager -n NUnit logs
whoami · sudo -n true 2>&1; echo EXIT=$? · whoami again through the sudo pathSUDO -nA 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 binaryThe 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 -25Process 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 -25Process 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 PIDSUDOKilling a process, behind a confirmation. Elevated because the list includes processes you don't own
ip -o addr · ip -o link · ss -tunlH | head -50The network screen, on Linux
ifconfig -a · netstat -an -p tcp · netstat -an -p udpThe network screen, on macOS
ping -c 4 -W 2 HOSTNetwork tools · ping
traceroute -n -w 2 -m 15 HOSTNetwork tools · traceroute
dig +short A|AAAA|MX HOST, falling back to host HOST then getent hosts HOSTNetwork 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 -aNetwork 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 typedThe terminal and your own saved commands run exactly what you wrote, nothing added

The file browserOnly while the Files screen is open.

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 assessmentRead-only except where marked. Runs from the dashboard, at most once a minute.

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_throttledUnder-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 piWhether 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 -nWhether 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 IFACEWhether 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.jsonThe 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/uptimeFlags an uptime over 90 days as a reboot you're probably overdue for, after kernel updates

The assessment's second batchOne script, same cadence, 20-second deadline.

date +%sThe 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 --valueRestart 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 1209600Certificates 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 -nDisk 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 -nFailed 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 -nWhether password login is actually in effect, which is not always what /etc/ssh/sshd_config says. A drop-in file can override it

How sudo is handled

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.