run the dev server as a system-wide systemd unit (boots without login/linger)
This commit is contained in:
parent
174212750b
commit
bfa0840b0e
4 changed files with 119 additions and 20 deletions
32
README.md
32
README.md
|
|
@ -114,9 +114,24 @@ site. To force the raw server instead, use `npm run dev:next` (don't point a tun
|
||||||
|
|
||||||
### Running it as a service (survives reboot)
|
### Running it as a service (survives reboot)
|
||||||
|
|
||||||
On this host the dev server runs as a **user** systemd unit, so it comes back after a
|
On this host the dev server runs as a **system** systemd unit, so it starts at boot on
|
||||||
reboot without a login (linger is enabled). The unit lives in the repo at
|
its own — no login or linger needed. The unit lives in the repo at
|
||||||
[`deploy/dvorakmortonxd-dev.service`](./deploy/dvorakmortonxd-dev.service):
|
[`deploy/dvorakmortonxd-dev.system.service`](./deploy/dvorakmortonxd-dev.system.service):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# installs /etc/systemd/system/dvorakmortonxd-dev.service and enables it
|
||||||
|
sudo bash deploy/install-system-service.sh # or: doas bash deploy/install-system-service.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Manage it with `systemctl status|restart|stop dvorakmortonxd-dev` and follow logs with
|
||||||
|
`journalctl -u dvorakmortonxd-dev -f`. It shares `.next/dev/lock` with a manual
|
||||||
|
`npm run dev`, so stop the service before starting one by hand.
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>No root? Use the per-user unit instead</summary>
|
||||||
|
|
||||||
|
[`deploy/dvorakmortonxd-dev.service`](./deploy/dvorakmortonxd-dev.service) is an equivalent
|
||||||
|
**user** unit you can install without root:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
mkdir -p ~/.config/systemd/user
|
mkdir -p ~/.config/systemd/user
|
||||||
|
|
@ -126,9 +141,10 @@ systemctl --user enable --now dvorakmortonxd-dev.service
|
||||||
loginctl enable-linger "$USER" # once, so it starts at boot
|
loginctl enable-linger "$USER" # once, so it starts at boot
|
||||||
```
|
```
|
||||||
|
|
||||||
Manage it with `systemctl --user status|restart|stop dvorakmortonxd-dev` and follow logs with
|
Drive it with `systemctl --user …` and `journalctl --user -u …`. Run only one of the two —
|
||||||
`journalctl --user -u dvorakmortonxd-dev -f`. It shares `.next/dev/lock` with a manual
|
they contend for `:3000` and `.next/dev/lock`.
|
||||||
`npm run dev`, so stop the service before starting one by hand.
|
|
||||||
|
</details>
|
||||||
|
|
||||||
## 📡 API Routes
|
## 📡 API Routes
|
||||||
|
|
||||||
|
|
@ -172,7 +188,9 @@ src/app/
|
||||||
scripts/
|
scripts/
|
||||||
dev-server.mjs → custom dev server: live updates without the dev-only debug surface
|
dev-server.mjs → custom dev server: live updates without the dev-only debug surface
|
||||||
deploy/
|
deploy/
|
||||||
dvorakmortonxd-dev.service → systemd user unit so the dev server starts at boot
|
dvorakmortonxd-dev.system.service → systemd unit (system-wide) so the dev server starts at boot
|
||||||
|
dvorakmortonxd-dev.service → equivalent per-user unit (no root; needs linger)
|
||||||
|
install-system-service.sh → installs the system unit (run with sudo/doas)
|
||||||
public/ → fonts, wallpapers, icons, friend pages, /revival-inc game, llms.txt, robots.txt, sitemap.xml
|
public/ → fonts, wallpapers, icons, friend pages, /revival-inc game, llms.txt, robots.txt, sitemap.xml
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
|
||||||
29
agents.md
29
agents.md
|
|
@ -249,15 +249,18 @@ list reordering, not free placement. Don't confuse the two.
|
||||||
`localhost:3000` keeps the full dev experience; the LAN/tunnel get a plain,
|
`localhost:3000` keeps the full dev experience; the LAN/tunnel get a plain,
|
||||||
live-reloading site. Use `npm run dev:next` only if you explicitly want the
|
live-reloading site. Use `npm run dev:next` only if you explicitly want the
|
||||||
raw, unprotected server (never point the tunnel at it).
|
raw, unprotected server (never point the tunnel at it).
|
||||||
- The dev server normally runs on this host as a **user** systemd unit
|
- The dev server runs on this host as a **system** systemd unit
|
||||||
(`~/.config/systemd/user/dvorakmortonxd-dev.service`, kept in-repo at
|
(`/etc/systemd/system/dvorakmortonxd-dev.service`, kept in-repo at
|
||||||
`deploy/dvorakmortonxd-dev.service`). `Linger=yes` for uid 1000 means it starts
|
`deploy/dvorakmortonxd-dev.system.service`, installed by
|
||||||
at boot without a login. `systemctl --user restart dvorakmortonxd-dev` reloads
|
`deploy/install-system-service.sh`). It is enabled at `multi-user.target`, so
|
||||||
it; logs with `journalctl --user -u dvorakmortonxd-dev -f`. From a bare shell
|
it starts at boot with no login/linger. `systemctl restart dvorakmortonxd-dev`
|
||||||
with no session bus, prefix those with
|
reloads it; logs with `journalctl -u dvorakmortonxd-dev -f`. The unit pins
|
||||||
`XDG_RUNTIME_DIR=/run/user/1000 DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus`.
|
`HOSTNAME=0.0.0.0` (the script reads that env var) so the tunnel can reach
|
||||||
The unit pins `HOSTNAME=0.0.0.0` (the script reads that env var) so the tunnel
|
:3000, clears a stale `.next/dev/lock` on start, and sets `HOME` because system
|
||||||
can reach :3000, and clears a stale `.next/dev/lock` on start.
|
units don't inherit a login environment. A per-user twin
|
||||||
|
(`deploy/dvorakmortonxd-dev.service`) exists for rootless installs; it needs
|
||||||
|
`loginctl enable-linger` and is driven via `systemctl --user`. Only one of the
|
||||||
|
two may run at a time — they contend for :3000 and `.next/dev/lock`.
|
||||||
- `allowedDevOrigins` (`next.config.ts`) still matters: the tunnel host and any
|
- `allowedDevOrigins` (`next.config.ts`) still matters: the tunnel host and any
|
||||||
LAN origin must be listed or Next rejects the HMR WebSocket with a 403 (the
|
LAN origin must be listed or Next rejects the HMR WebSocket with a 403 (the
|
||||||
client then never hydrates — SSR only, icons stacked at `(0,0)`, dead
|
client then never hydrates — SSR only, icons stacked at `(0,0)`, dead
|
||||||
|
|
@ -309,10 +312,10 @@ list reordering, not free placement. Don't confuse the two.
|
||||||
- There is **no test suite**. Verification is `tsc --noEmit`, `eslint`, and
|
- There is **no test suite**. Verification is `tsc --noEmit`, `eslint`, and
|
||||||
`next build`.
|
`next build`.
|
||||||
- A dev server may already be running on port 3000 — on this host it is normally
|
- A dev server may already be running on port 3000 — on this host it is normally
|
||||||
the `dvorakmortonxd-dev` user systemd service, so check
|
the **system** `dvorakmortonxd-dev` service, so check
|
||||||
`systemctl --user status dvorakmortonxd-dev` first. Two dev servers on this
|
`systemctl status dvorakmortonxd-dev` first. Two dev servers on this repo
|
||||||
repo collide on `.next/dev/lock`; stop the service (or the old process)
|
collide on `.next/dev/lock`; stop the service (or the old process) before
|
||||||
before starting the custom server by hand.
|
starting the custom server by hand.
|
||||||
- Dev-server guard check: `curl -s -o /dev/null -w '%{http_code}' \
|
- Dev-server guard check: `curl -s -o /dev/null -w '%{http_code}' \
|
||||||
http://localhost:3000/__nextjs_server_status` must be `200` (localhost keeps
|
http://localhost:3000/__nextjs_server_status` must be `200` (localhost keeps
|
||||||
debug), while the same path through the LAN IP or `beta.dvorakmortonxd.dev`
|
debug), while the same path through the LAN IP or `beta.dvorakmortonxd.dev`
|
||||||
|
|
|
||||||
31
deploy/dvorakmortonxd-dev.system.service
Normal file
31
deploy/dvorakmortonxd-dev.system.service
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
[Unit]
|
||||||
|
Description=dvorakmortonxd.dev dev server (custom Next.js dev wrapper)
|
||||||
|
Documentation=file:///home/martin/dvorakmortonxd.dev/README.md
|
||||||
|
After=network-online.target
|
||||||
|
Wants=network-online.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
User=martin
|
||||||
|
Group=martin
|
||||||
|
WorkingDirectory=/home/martin/dvorakmortonxd.dev
|
||||||
|
# A system service does not inherit a login environment, so set HOME/PATH
|
||||||
|
# explicitly (Next.js wants a writable HOME for its cache).
|
||||||
|
Environment=HOME=/home/martin
|
||||||
|
Environment=PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
||||||
|
# Bind all interfaces so the Cloudflare tunnel host can reach :3000. The dev
|
||||||
|
# server reads process.env.HOSTNAME, so pin it rather than inheriting it.
|
||||||
|
Environment=HOSTNAME=0.0.0.0
|
||||||
|
Environment=PORT=3000
|
||||||
|
# A reboot can leave a stale dev lock behind; clear it before starting.
|
||||||
|
ExecStartPre=/bin/rm -f /home/martin/dvorakmortonxd.dev/.next/dev/lock
|
||||||
|
ExecStart=/bin/node /home/martin/dvorakmortonxd.dev/scripts/dev-server.mjs
|
||||||
|
Restart=always
|
||||||
|
RestartSec=3
|
||||||
|
# Give Next.js a moment to shut down cleanly.
|
||||||
|
TimeoutStopSec=15
|
||||||
|
StandardOutput=journal
|
||||||
|
StandardError=journal
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
47
deploy/install-system-service.sh
Executable file
47
deploy/install-system-service.sh
Executable file
|
|
@ -0,0 +1,47 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
# Install the dvorakmortonxd.dev dev server as a SYSTEM systemd unit so it
|
||||||
|
# starts at boot for the whole machine (no login / no linger needed).
|
||||||
|
#
|
||||||
|
# Run it with root, e.g.:
|
||||||
|
# doas bash deploy/install-system-service.sh
|
||||||
|
#
|
||||||
|
# It also retires the per-user unit, so the two don't fight over :3000 and
|
||||||
|
# .next/dev/lock.
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
|
echo "root required — run: doas bash \"$0\"" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
here="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
unit_src="$here/dvorakmortonxd-dev.system.service"
|
||||||
|
unit_dst="/etc/systemd/system/dvorakmortonxd-dev.service"
|
||||||
|
user_unit="/home/martin/.config/systemd/user/dvorakmortonxd-dev.service"
|
||||||
|
|
||||||
|
install -m 0644 "$unit_src" "$unit_dst"
|
||||||
|
systemctl daemon-reload
|
||||||
|
|
||||||
|
# Retire the per-user copy if it exists (installed earlier as martin).
|
||||||
|
if [ -e "$user_unit" ] || [ -e "/home/martin/.config/systemd/user/default.target.wants/dvorakmortonxd-dev.service" ]; then
|
||||||
|
if command -v runuser >/dev/null 2>&1; then
|
||||||
|
runuser -u martin -- env XDG_RUNTIME_DIR=/run/user/1000 \
|
||||||
|
DBUS_SESSION_BUS_ADDRESS=unix:path=/run/user/1000/bus \
|
||||||
|
systemctl --user disable --now dvorakmortonxd-dev.service || true
|
||||||
|
else
|
||||||
|
# runuser is unavailable (minimal container). The caller should have run
|
||||||
|
# `systemctl --user stop dvorakmortonxd-dev` first; we still remove the files
|
||||||
|
# so the two units can't fight over :3000 on the next boot.
|
||||||
|
echo "note: runuser not found — stop the per-user unit manually if it is still running" >&2
|
||||||
|
fi
|
||||||
|
rm -f "$user_unit" \
|
||||||
|
/home/martin/.config/systemd/user/default.target.wants/dvorakmortonxd-dev.service
|
||||||
|
fi
|
||||||
|
|
||||||
|
systemctl enable --now dvorakmortonxd-dev.service
|
||||||
|
systemctl --no-pager --full status dvorakmortonxd-dev.service || true
|
||||||
|
|
||||||
|
echo
|
||||||
|
echo "Installed. Manage it with:"
|
||||||
|
echo " systemctl status|restart|stop dvorakmortonxd-dev"
|
||||||
|
echo " journalctl -u dvorakmortonxd-dev -f"
|
||||||
Loading…
Add table
Add a link
Reference in a new issue