40ec0c85062bc2788f80ff5e06fe393a6b62c8b8
9router Start on Boot (without systemd)
Make 9router (AI router & token saver) auto-start as a non-root user (ardhi)
when the server boots — using a @reboot cron job instead of a systemd unit.
Proven working on Ubuntu 26.04 (systemd) after a real reboot.
Why not systemd / plain 9router?
- Running
9routerwithout-tcrashes on a headless server (it waits for an interactive TTY menu that doesn't exist → crash-loop withEADDRINUSE). - A plain
@rebootcron calling9router -t ...directly fails because:- cron runs the job as the cron user, not root — so
runuserinside is rejected (runuser: may not be used by non-root users). - The log file gets created as root, so the
ardhiprocess can't write to it (cannot create /home/ardhi/9router.log: Permission denied). - The process is killed when cron's boot session tears down
(
Session terminated, killing shell... killed.).
- cron runs the job as the cron user, not root — so
This project solves all three with a tiny wrapper script + an adaptive crontab.
Files
| File | Purpose |
|---|---|
start-9router.sh |
Detached launcher. Drops to ardhi if run as root, runs directly if already ardhi. Uses absolute paths (cron @reboot has a minimal PATH). |
install.sh |
One-shot setup: installs the wrapper, fixes log ownership, and registers the @reboot cron as root. |
README.md |
This file. |
Step-by-step (manual)
1. Install the wrapper
sudo cp start-9router.sh /usr/local/bin/start-9router.sh
sudo chmod +x /usr/local/bin/start-9router.sh
2. Fix log-file ownership (so ardhi can write)
sudo touch /home/ardhi/9router.log
sudo chown ardhi:ardhi /home/ardhi/9router.log
sudo chmod 644 /home/ardhi/9router.log
3. Register the @reboot cron as ROOT
Must be root's crontab, because the wrapper uses
runuserto drop privileges.
sudo crontab -l 2>/dev/null; echo "@reboot /usr/local/bin/start-9router.sh" | sudo crontab -
Verify:
sudo crontab -l
# -> @reboot /usr/local/bin/start-9router.sh
4. Make sure the cron daemon is running
sudo systemctl enable cron
sudo systemctl start cron
systemctl is-active cron # -> active
5. Disable the (broken) systemd unit if it exists
sudo systemctl disable 9router 2>/dev/null || true
6. Reboot and verify
sudo reboot
After the server is back:
# port should be LISTEN
ss -ltnp | grep 20128
# process should run as ardhi
ps -eo pid,user,args | grep -E '9router -t|next-server' | grep -v grep
# live check
curl -s -o /dev/null -w "%{http_code}\n" http://127.0.0.1:20128/v1/models # -> 200
Dashboard: http://127.0.0.1:20128/dashboard API endpoint for CLI tools: http://127.0.0.1:20128/v1
Or just run the installer
sudo ./install.sh
Then reboot.
How the wrapper works
if [ "$(id -u)" = "0" ]; then
# root (e.g. root's @reboot cron) -> drop to ardhi
setsid runuser -u ardhi -- sh -c "exec '9router' -t -n --host 127.0.0.1 --port 20128 >> LOG 2>&1 < /dev/null" &
else
# already ardhi -> run directly
setsid 9router -t -n --host 127.0.0.1 --port 20128 >> LOG 2>&1 < /dev/null &
fi
setsid+< /dev/null→ detaches the process from cron's session so it survives the boot-session teardown.- Absolute paths (
/usr/bin/setsid,/usr/sbin/runuser,/usr/local/bin/9router) → required because cron@rebootruns with a minimalPATH. - The redirect happens inside
runuser, soardhi(not root) owns the log file.
Notes / caveats
@rebootcron starts the service at boot but does not auto-restart it if the parent-tprocess dies. For extra safety you could add a per-minute watchdog cron that checks the port and re-launches the wrapper if needed.- Change the user (
ardhi), port (20128), or bind host (127.0.0.1) by editing the wrapper directly. - This setup was validated on the
9router@0.5.50standalone build, which crashes when launched without-ton a headless machine.
License
MIT — use freely.
Description
Languages
Shell
100%