Add 9router start-on-boot setup (non-root, @reboot cron, no systemd)
This commit is contained in:
9
.gitignore
vendored
Normal file
9
.gitignore
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# ignore OS / editor junk
|
||||||
|
.DS_Store
|
||||||
|
*.swp
|
||||||
|
*~
|
||||||
|
.idea/
|
||||||
|
.vscode/
|
||||||
|
|
||||||
|
# don't commit the reboot marker
|
||||||
|
/tmp/9router-cron-ran
|
||||||
136
README.md
Normal file
136
README.md
Normal file
@@ -0,0 +1,136 @@
|
|||||||
|
# 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 `9router` **without** `-t` crashes on a headless server (it waits for an
|
||||||
|
interactive TTY menu that doesn't exist → crash-loop with `EADDRINUSE`).
|
||||||
|
- A plain `@reboot` cron calling `9router -t ...` directly fails because:
|
||||||
|
1. cron runs the job as the **cron user**, not root — so `runuser` inside is rejected
|
||||||
|
(`runuser: may not be used by non-root users`).
|
||||||
|
2. The log file gets created as **root**, so the `ardhi` process can't write to it
|
||||||
|
(`cannot create /home/ardhi/9router.log: Permission denied`).
|
||||||
|
3. The process is killed when cron's boot session tears down
|
||||||
|
(`Session terminated, killing shell... killed.`).
|
||||||
|
|
||||||
|
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
|
||||||
|
```bash
|
||||||
|
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)
|
||||||
|
```bash
|
||||||
|
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 `runuser` to drop privileges.
|
||||||
|
```bash
|
||||||
|
sudo crontab -l 2>/dev/null; echo "@reboot /usr/local/bin/start-9router.sh" | sudo crontab -
|
||||||
|
```
|
||||||
|
Verify:
|
||||||
|
```bash
|
||||||
|
sudo crontab -l
|
||||||
|
# -> @reboot /usr/local/bin/start-9router.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Make sure the cron daemon is running
|
||||||
|
```bash
|
||||||
|
sudo systemctl enable cron
|
||||||
|
sudo systemctl start cron
|
||||||
|
systemctl is-active cron # -> active
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Disable the (broken) systemd unit if it exists
|
||||||
|
```bash
|
||||||
|
sudo systemctl disable 9router 2>/dev/null || true
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. Reboot and verify
|
||||||
|
```bash
|
||||||
|
sudo reboot
|
||||||
|
```
|
||||||
|
After the server is back:
|
||||||
|
```bash
|
||||||
|
# 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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./install.sh
|
||||||
|
```
|
||||||
|
Then reboot.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## How the wrapper works
|
||||||
|
|
||||||
|
```sh
|
||||||
|
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 `@reboot` runs with a minimal `PATH`.
|
||||||
|
- The redirect happens **inside** `runuser`, so `ardhi` (not root) owns the log file.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Notes / caveats
|
||||||
|
|
||||||
|
- `@reboot` cron starts the service at boot but does **not** auto-restart it if the
|
||||||
|
parent `-t` process 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.50` standalone build, which crashes when
|
||||||
|
launched without `-t` on a headless machine.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## License
|
||||||
|
MIT — use freely.
|
||||||
32
install.sh
Executable file
32
install.sh
Executable file
@@ -0,0 +1,32 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# install.sh — set up 9router auto-start on boot (non-root, no systemd).
|
||||||
|
# Run as root: sudo ./install.sh
|
||||||
|
set -e
|
||||||
|
|
||||||
|
WRAPPER_SRC="./start-9router.sh"
|
||||||
|
WRAPPER_DST="/usr/local/bin/start-9router.sh"
|
||||||
|
LOG="/home/ardhi/9router.log"
|
||||||
|
USER="ardhi"
|
||||||
|
|
||||||
|
echo "==> Installing wrapper to $WRAPPER_DST"
|
||||||
|
install -m 0755 "$WRAPPER_SRC" "$WRAPPER_DST"
|
||||||
|
|
||||||
|
echo "==> Preparing log file ($LOG) owned by $USER"
|
||||||
|
touch "$LOG"
|
||||||
|
chown "$USER:$USER" "$LOG"
|
||||||
|
chmod 644 "$LOG"
|
||||||
|
|
||||||
|
echo "==> Registering @reboot cron as ROOT"
|
||||||
|
# Must be root's crontab (wrapper uses runuser to drop privileges).
|
||||||
|
( crontab -l 2>/dev/null | grep -v 'start-9router.sh'; echo "@reboot $WRAPPER_DST" ) | crontab -
|
||||||
|
|
||||||
|
echo "==> Ensuring cron daemon is active"
|
||||||
|
systemctl enable cron >/dev/null 2>&1 || true
|
||||||
|
systemctl start cron >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
echo "==> Disabling broken systemd unit (if any)"
|
||||||
|
systemctl disable 9router >/dev/null 2>&1 || true
|
||||||
|
|
||||||
|
echo "==> Done. Reboot to verify:"
|
||||||
|
echo " ss -ltnp | grep 20128"
|
||||||
|
echo " curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:20128/v1/models"
|
||||||
26
start-9router.sh
Executable file
26
start-9router.sh
Executable file
@@ -0,0 +1,26 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
# Start 9router detached at boot.
|
||||||
|
# Runs as the current user; if that's root, drop privileges to 'ardhi'.
|
||||||
|
# Uses absolute paths (cron @reboot has a minimal PATH).
|
||||||
|
|
||||||
|
SETSID=/usr/bin/setsid
|
||||||
|
RUNUSER=/usr/sbin/runuser
|
||||||
|
BIN=/usr/local/bin/9router
|
||||||
|
LOG=/home/ardhi/9router.log
|
||||||
|
|
||||||
|
touch /tmp/9router-cron-ran 2>/dev/null
|
||||||
|
|
||||||
|
if [ ! -x "$BIN" ]; then
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "$(id -u)" = "0" ]; then
|
||||||
|
# Running as root (e.g. root's @reboot cron): drop to ardhi.
|
||||||
|
"$SETSID" "$RUNUSER" -u ardhi -- sh -c \
|
||||||
|
"exec '$BIN' -t -n --host 127.0.0.1 --port 20128 >> '$LOG' 2>&1 < /dev/null" &
|
||||||
|
else
|
||||||
|
# Already running as ardhi: start directly.
|
||||||
|
"$SETSID" "$BIN" -t -n --host 127.0.0.1 --port 20128 >> "$LOG" 2>&1 < /dev/null &
|
||||||
|
fi
|
||||||
|
disown 2>/dev/null || true
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user