- Trading skills (OKX, dividend, lottery, quantitative) - Creative skills (ASCII art, diagrams, video) - Development skills (GitHub, debugging, TDD) - Research skills (arXiv, blog monitoring) - Productivity skills (email, documents, notes) - MCP integration skills - Custom user skills
136 lines
7.6 KiB
Markdown
136 lines
7.6 KiB
Markdown
---
|
|
name: startup-service-manager
|
|
description: "Generic Linux service manager script — start/stop/restart/status any service via simple .sh definitions. No systemd required."
|
|
tags: [devops, services, startup, process-management, linux]
|
|
---
|
|
|
|
# Startup - Generic Service Manager
|
|
|
|
A lightweight bash-based service manager at `~/startup/`. Manages any process (mihomo, yacd, custom apps) via simple `.sh` definition files. No systemd, no Docker dependency.
|
|
|
|
## When to Use
|
|
|
|
- User wants to manage multiple background services from one place
|
|
- Services don't have their own systemd units
|
|
- Quick prototyping before committing to systemd
|
|
- User explicitly asks for a startup/service management script
|
|
|
|
## Architecture
|
|
|
|
```
|
|
~/startup/
|
|
├── startup.sh # Main script
|
|
├── startup # Symlink/wrapper for convenience
|
|
├── services/ # One .sh per service (the registry)
|
|
│ ├── mihomo.sh
|
|
│ └── yacd.sh
|
|
├── pids/ # Auto-managed PID files
|
|
└── logs/ # Per-service stdout/stderr logs
|
|
```
|
|
|
|
## Commands
|
|
|
|
```bash
|
|
bash ~/startup/startup.sh status # Show all services (running/stopped + PID)
|
|
bash ~/startup/startup.sh list # List all registered services (name + port + desc)
|
|
bash ~/startup/startup.sh start # Start all
|
|
bash ~/startup/startup.sh stop # Stop all
|
|
bash ~/startup/startup.sh restart mihomo # Restart one
|
|
bash ~/startup/startup.sh log mihomo # View logs
|
|
bash ~/startup/startup.sh register nginx # Create template
|
|
bash ~/startup/startup.sh remove nginx # Delete service
|
|
bash ~/startup/startup.sh help # Show help
|
|
```
|
|
|
|
`status` vs `list`: `status` shows runtime state (PID, running/stopped), `list` shows a clean registry view (name, port, description).
|
|
|
|
If alias is set (`alias startup="bash ~/startup/startup.sh"`), just type `startup status`.
|
|
|
|
## Service Definition Format
|
|
|
|
Each service is a `.sh` file in `~/startup/services/`. Example (`mihomo.sh`):
|
|
|
|
```bash
|
|
SVC_DESC="Clash/Mihomo 代理引擎"
|
|
SVC_PORT="7890 (代理) / 9090 (API)"
|
|
SVC_PID_PATTERN="mihomo -d"
|
|
SVC_START="cd ~/clash && exec ./mihomo -d config"
|
|
SVC_HEALTH="curl -s http://127.0.0.1:9090/version"
|
|
```
|
|
|
|
**Required:** `SVC_START` — the command to launch the service (must background itself or use `exec` with the script's `&`).
|
|
|
|
**Optional:**
|
|
| Variable | Purpose |
|
|
|----------|---------|
|
|
| `SVC_DESC` | Human-readable description (shown in `status`) |
|
|
| `SVC_PORT` | Port info string (display only) |
|
|
| `SVC_PID_PATTERN` | `pkill -f` pattern for cleanup of stale processes |
|
|
| `SVC_HEALTH` | Command that exits 0 when service is ready (used for startup wait) |
|
|
| `SVC_STOP` | Custom stop command (before SIGTERM) |
|
|
|
|
## Registering a New Service
|
|
|
|
```bash
|
|
# Method 1: Use register command (creates template)
|
|
bash ~/startup/startup.sh register myapp
|
|
# Then edit ~/startup/services/myapp.sh
|
|
|
|
# Method 2: Write directly
|
|
cat > ~/startup/services/myapp.sh << 'EOF'
|
|
SVC_DESC="My Application"
|
|
SVC_PORT="8080"
|
|
SVC_PID_PATTERN="myapp"
|
|
SVC_START="cd ~/myapp && exec node server.js"
|
|
SVC_HEALTH="curl -s http://127.0.0.1:8080/health"
|
|
EOF
|
|
```
|
|
|
|
## Quick-Start Service Definitions
|
|
|
|
Ready-to-use definitions for common services: `skill_view(name="startup-service-manager", file_path="references/common-services.md")`
|
|
|
|
## Auto-Start with systemd
|
|
|
|
To make startup services auto-start on boot, create a systemd unit:
|
|
|
|
```bash
|
|
cat > /tmp/startup-services.service << 'EOF'
|
|
[Unit]
|
|
Description=Startup Services (mihomo, yacd, hermes-dashboard)
|
|
After=network.target
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
RemainAfterExit=yes
|
|
User=openclaw
|
|
ExecStart=/home/openclaw/startup/startup.sh start all
|
|
ExecStop=/home/openclaw/startup/startup.sh stop all
|
|
|
|
[Install]
|
|
WantedBy=multi-user.target
|
|
EOF
|
|
|
|
sudo cp /tmp/startup-services.service /etc/systemd/system/
|
|
sudo systemctl daemon-reload
|
|
sudo systemctl enable startup-services.service
|
|
```
|
|
|
|
**Important:** `startup.sh` must have execute permission: `chmod +x ~/startup/startup.sh`
|
|
|
|
## Pitfalls
|
|
|
|
- **PID file stale after crash**: If a service crashes without cleanup, the PID file remains. The script checks `kill -0` so it detects dead PIDs, but `start` will refuse. Fix: `rm ~/startup/pids/<name>.pid` then start again, or just `startup restart <name>` which stops (cleans up) then starts.
|
|
- **systemd permission denied**: If systemd fails with "Permission denied" when executing startup.sh, run `chmod +x ~/startup/startup.sh`. The script needs execute permission for systemd to run it.
|
|
- **Multiple processes on same pattern**: `SVC_PID_PATTERN` is used with `pkill -f`, which kills ALL matching processes. If the pattern is too broad (e.g., just `python`), it will kill unrelated processes. Use specific patterns like `http.server 9091`.
|
|
- **Health check timeout**: Default wait is 10 seconds. If your service takes longer to start, the script still succeeds (PID is alive) but logs "启动成功" before health check passes. For slow-starting services, increase the loop or accept the delay.
|
|
- **Log rotation**: Logs append (`>>`), never rotate. For long-running production, either add logrotate or periodically truncate: `> ~/startup/logs/mihomo.log`.
|
|
- **startup.sh vs systemd**: Don't use both for the same service — they'll fight over PIDs. Pick one management system per service.
|
|
- **⚠️ CRITICAL: systemd cgroup kills child processes on exit**: If `startup.sh restart <service>` is called from inside a systemd oneshot service (e.g., a watchdog timer), the spawned process inherits the service's cgroup. When the oneshot service exits, **systemd kills all processes in the cgroup — including the just-started service**. This creates a death loop: watchdog starts service → watchdog exits → systemd kills service → next watchdog cycle finds service dead → repeat. **Fix**: Services managed by a systemd-timer watchdog MUST run as their own dedicated systemd service. The watchdog should call `systemctl --user restart <service>` instead of `startup.sh restart <service>`. See `skill_view(name="clash-docker-workflow", file_path="references/mihomo-watchdog.md")` for the full diagnosis and fix.
|
|
- **Alias not available in new terminals**: After adding `alias startup=...` to `.bashrc`, existing terminals need `source ~/.bashrc`. New terminals get it automatically.
|
|
- **Tracking existing processes**: When a service was started outside startup.sh (manually, or by another system), the PID file won't exist. The script will show "已停止" even though the process is running. Fix: `echo $(pgrep -f "pattern") > ~/startup/pids/<name>.pid`. Or restart through startup.sh to take over management.
|
|
- **Duplicate instances**: If you manually start a service that's already running via startup.sh, you get two processes competing for the same port. Always check `startup status` before starting. The `stop` command uses both PID file AND `pkill -f SVC_PID_PATTERN` to catch stragglers.
|
|
- **SVC_HEALTH with curl -w format**: In `eval` context, `curl -w '%{http_code}'` can misbehave because `%` and `{}` are interpreted by the shell. Use double quotes inside: `curl -s ... -o /dev/null -w '"'"'%{http_code}'"'"'` or test with a simpler check like `ss -tlnp | grep -q PORT`.
|
|
- **Alias in .bashrc**: When adding `alias startup=...` to `.bashrc`, the line `grep -q || echo >> ~/.bashrc` pattern needs the user's approval for writing to .bashrc. Use `write_file` or ask user consent.
|
|
- **Health watchdog vs service manager**: startup.sh manages start/stop lifecycle but doesn't monitor ongoing health. For services that need continuous health monitoring (e.g., mihomo proxy whose crash cascades to Telegram adapter), use a watchdog pattern — see `skill_view(name="clash-docker-workflow", file_path="references/mihomo-watchdog.md")`.
|