99 lines
4.5 KiB
Markdown
99 lines
4.5 KiB
Markdown
# Clusev restart sentinel (host watcher)
|
|
|
|
One-click stack restart from the dashboard — **without giving the container the Docker
|
|
socket**.
|
|
|
|
## How it works
|
|
|
|
```
|
|
Dashboard (app container) Host
|
|
───────────────────────── ────
|
|
System → "Jetzt neu starten"
|
|
│
|
|
▼
|
|
DeploymentService::requestRestart()
|
|
writes storage/app/restart-signal/restart.request
|
|
│ (shared bind mount: ./run on the host)
|
|
▼
|
|
./run/restart.request appears ──────────► systemd clusev-restart.path
|
|
│ (PathExists=)
|
|
▼
|
|
clusev-restart.service (oneshot)
|
|
│ runs watch.sh once
|
|
▼
|
|
docker compose -f docker-compose.prod.yml up -d
|
|
rm ./run/restart.request
|
|
```
|
|
|
|
The app only ever **writes a marker file**. A scoped host-side systemd unit does the
|
|
restart. The file content is a non-sensitive ISO timestamp; the trigger is the file
|
|
*existing*, not what it holds.
|
|
|
|
## Files
|
|
|
|
| File | Role |
|
|
|---|---|
|
|
| `watch.sh` | The actual logic: `once` (systemd) restarts the stack + deletes the sentinel; `update` runs `update.sh` (git pull + re-install); `loop` is a standalone inotify/poll fallback for hosts without systemd (restart only). |
|
|
| `clusev-restart.path` | systemd `.path` unit — `PathExists=` the restart sentinel, triggers the restart service. |
|
|
| `clusev-restart.service` | systemd oneshot — runs `watch.sh once` as the `clusev` user. |
|
|
| `clusev-update.path` | systemd `.path` unit — `PathExists=` the update sentinel (`run/update.request`), triggers the update service. |
|
|
| `clusev-update.service` | systemd oneshot — runs `watch.sh update` as **root** (update.sh → install.sh needs root). |
|
|
|
|
The update sentinel (dashboard → Version & Releases → "Jetzt aktualisieren") works exactly like the
|
|
restart one, but the watcher runs `update.sh` instead of a plain restart, and consumes the sentinel
|
|
*before* running so a persistent pull failure can't loop. `install.sh` installs both pairs of units.
|
|
|
|
## Install (systemd — preferred)
|
|
|
|
From the project root (default path `/home/nexxo/clusev`; adjust the units if yours differs):
|
|
|
|
```bash
|
|
sudo cp docker/restart-sentinel/clusev-restart.path /etc/systemd/system/
|
|
sudo cp docker/restart-sentinel/clusev-restart.service /etc/systemd/system/
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable --now clusev-restart.path
|
|
```
|
|
|
|
Check it:
|
|
|
|
```bash
|
|
systemctl status clusev-restart.path
|
|
# Trigger a test restart by hand:
|
|
touch ./run/restart.request
|
|
journalctl -u clusev-restart.service -f
|
|
```
|
|
|
|
Both units hard-code `/home/nexxo/clusev`. If your project lives elsewhere, edit
|
|
`PathExists=` (in `.path`), and `WorkingDirectory=` / `Environment=CLUSEV_DIR=` /
|
|
`ExecStart=` (in `.service`) before copying. The `User=` in the service must be able to
|
|
run `docker compose` (root, or a member of the `docker` group).
|
|
|
|
## Install (no systemd — poll/inotify fallback)
|
|
|
|
Run the watcher as a long-lived loop (e.g. under your own supervisor, tmux, or `nohup`):
|
|
|
|
```bash
|
|
CLUSEV_DIR=/home/nexxo/clusev docker/restart-sentinel/watch.sh loop
|
|
```
|
|
|
|
It uses `inotifywait` if available (`apt-get install inotify-tools`), otherwise polls
|
|
every `CLUSEV_POLL` seconds (default 5).
|
|
|
|
## Security note
|
|
|
|
- The **container gets NO Docker socket** — it cannot run `docker` at all. It only writes
|
|
`storage/app/restart-signal/restart.request` on a shared bind mount.
|
|
- The restart is performed by a **scoped host systemd unit** (`clusev-restart.service`),
|
|
the only component permitted to drive Docker. A panel compromise therefore cannot run
|
|
arbitrary `docker` commands — at worst it can request a restart of the same stack.
|
|
- `watch.sh` only deletes the sentinel **after a successful** `docker compose up -d`, so a
|
|
transient failure is retried on the next trigger rather than silently lost.
|
|
|
|
## Config (watch.sh env)
|
|
|
|
| Var | Default | Meaning |
|
|
|---|---|---|
|
|
| `CLUSEV_DIR` | repo root (resolved from the script path) | dir holding `docker-compose.prod.yml` |
|
|
| `CLUSEV_SIGNAL` | `$CLUSEV_DIR/run/restart.request` | absolute path of the sentinel file |
|
|
| `CLUSEV_POLL` | `5` | poll interval (seconds) for the no-inotify loop fallback |
|