Template
feat: add prefixed service initializer and packaged deployment
This commit is contained in:
@@ -1,4 +1,7 @@
|
|||||||
name: 'Build & Deploy'
|
name: 'Build & Deploy'
|
||||||
|
env:
|
||||||
|
DEPLOY_DIR: /root
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
tags:
|
tags:
|
||||||
@@ -17,15 +20,14 @@ jobs:
|
|||||||
go build -ldflags="-s -w" -o server ./cmd/main_program
|
go build -ldflags="-s -w" -o server ./cmd/main_program
|
||||||
go build -ldflags="-s -w" -o cli ./cmd/cli_control
|
go build -ldflags="-s -w" -o cli ./cmd/cli_control
|
||||||
go build -ldflags="-s -w" -o scheduler ./cmd/scheduler
|
go build -ldflags="-s -w" -o scheduler ./cmd/scheduler
|
||||||
|
go build -ldflags="-s -w" -o initializer .
|
||||||
|
tar -czf deployment.tar.gz server cli scheduler initializer
|
||||||
|
|
||||||
- name: Save artifact
|
- name: Save artifact
|
||||||
uses: actions/upload-artifact@v3
|
uses: actions/upload-artifact@v3
|
||||||
with:
|
with:
|
||||||
name: build-artifacts
|
name: build-artifacts
|
||||||
path: |
|
path: ./deployment.tar.gz
|
||||||
./server
|
|
||||||
./cli
|
|
||||||
./scheduler
|
|
||||||
|
|
||||||
deploy:
|
deploy:
|
||||||
needs: build
|
needs: build
|
||||||
@@ -53,11 +55,9 @@ jobs:
|
|||||||
while read -r ip; do
|
while read -r ip; do
|
||||||
if [ -n "$ip" ]; then
|
if [ -n "$ip" ]; then
|
||||||
echo "Deploying to $ip..."
|
echo "Deploying to $ip..."
|
||||||
scp -o StrictHostKeyChecking=no server cli scheduler ${{ vars.ROOT_USER_NAME }}@"$ip":/root/
|
scp -o StrictHostKeyChecking=no deployment.tar.gz ${{ vars.ROOT_USER_NAME }}@"$ip":"$DEPLOY_DIR/deployment.tar.gz"
|
||||||
ssh -n ${{ vars.ROOT_USER_NAME }}@"$ip" << 'ENDSSH'
|
ssh -n ${{ vars.ROOT_USER_NAME }}@"$ip" \
|
||||||
chmod +x /root/server /root/cli /root/scheduler
|
"set -e; bash '$DEPLOY_DIR/stop.sh'; tar -xzf '$DEPLOY_DIR/deployment.tar.gz' -C '$DEPLOY_DIR'; chmod +x '$DEPLOY_DIR/server' '$DEPLOY_DIR/cli' '$DEPLOY_DIR/scheduler' '$DEPLOY_DIR/initializer'; bash '$DEPLOY_DIR/start.sh'"
|
||||||
systemctl restart server cli scheduler
|
|
||||||
ENDSSH
|
|
||||||
echo "Deployment to $ip completed"
|
echo "Deployment to $ip completed"
|
||||||
fi
|
fi
|
||||||
done < server_list.txt
|
done < server_list.txt
|
||||||
|
|||||||
@@ -11,6 +11,11 @@
|
|||||||
/vendor/
|
/vendor/
|
||||||
/logs/
|
/logs/
|
||||||
/tmp/
|
/tmp/
|
||||||
|
/.service-prefix
|
||||||
|
/server
|
||||||
|
/cli
|
||||||
|
/scheduler
|
||||||
|
/initializer
|
||||||
|
|
||||||
__debug_bin*
|
__debug_bin*
|
||||||
.idea/
|
.idea/
|
||||||
|
|||||||
@@ -52,6 +52,7 @@ go run ./cmd/scheduler # 定时任务调度器
|
|||||||
go build -ldflags="-s -w" -o server ./cmd/main_program
|
go build -ldflags="-s -w" -o server ./cmd/main_program
|
||||||
go build -ldflags="-s -w" -o cli ./cmd/cli_control
|
go build -ldflags="-s -w" -o cli ./cmd/cli_control
|
||||||
go build -ldflags="-s -w" -o scheduler ./cmd/scheduler
|
go build -ldflags="-s -w" -o scheduler ./cmd/scheduler
|
||||||
|
go build -ldflags="-s -w" -o initializer .
|
||||||
```
|
```
|
||||||
|
|
||||||
## 模板使用步骤
|
## 模板使用步骤
|
||||||
@@ -269,9 +270,20 @@ go build -ldflags="-s -w" -o scheduler ./cmd/scheduler
|
|||||||
|
|
||||||
### 首次安装(systemd 服务注册)
|
### 首次安装(systemd 服务注册)
|
||||||
|
|
||||||
|
`initializer` 已内置 `.env.example`、`install.sh`、启停脚本和 `deploy/*.service`。将它与三个业务二进制放到部署目录后执行:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
chmod +x install.sh start.sh stop.sh restart.sh server cli scheduler
|
chmod +x initializer server cli scheduler
|
||||||
sudo bash install.sh
|
sudo ./initializer
|
||||||
|
# 请输入项目前缀(例如 myapp)
|
||||||
|
```
|
||||||
|
|
||||||
|
初始化工具会自动释放部署文件、添加执行权限、把前缀保存到 `.service-prefix`,并在 `.env` 不存在时根据 `.env.example` 自动创建它,随后执行 `install.sh`。已有 `.env` 会被保留,不会覆盖其中的密钥和环境配置。注册出的服务名为 `myapp-server`、`myapp-cli` 和 `myapp-scheduler`。
|
||||||
|
|
||||||
|
无人值守初始化时可直接传入前缀:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ./initializer -prefix myapp
|
||||||
```
|
```
|
||||||
|
|
||||||
### 日常运维
|
### 日常运维
|
||||||
@@ -285,11 +297,11 @@ bash restart.sh # 重启全部
|
|||||||
单独管理:
|
单独管理:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
systemctl status server # 主服务状态
|
systemctl status myapp-server # 主服务状态
|
||||||
systemctl status scheduler # 调度器状态
|
systemctl status myapp-scheduler # 调度器状态
|
||||||
systemctl restart scheduler # 只重启调度器
|
systemctl restart myapp-scheduler # 只重启调度器
|
||||||
journalctl -u server -f # 主服务实时日志
|
journalctl -u myapp-server -f # 主服务实时日志
|
||||||
journalctl -u scheduler -f # 调度器实时日志
|
journalctl -u myapp-scheduler -f # 调度器实时日志
|
||||||
```
|
```
|
||||||
|
|
||||||
### systemd 服务特性
|
### systemd 服务特性
|
||||||
@@ -309,7 +321,9 @@ journalctl -u scheduler -f # 调度器实时日志
|
|||||||
├── server # HTTP 主服务
|
├── server # HTTP 主服务
|
||||||
├── cli # httplog 上报
|
├── cli # httplog 上报
|
||||||
├── scheduler # 定时任务调度器
|
├── scheduler # 定时任务调度器
|
||||||
|
├── initializer # 首次安装工具(内置部署资源)
|
||||||
├── .env # 环境配置
|
├── .env # 环境配置
|
||||||
|
├── .service-prefix # 初始化时保存的 systemd 服务名前缀
|
||||||
├── deploy/
|
├── deploy/
|
||||||
│ ├── server.service
|
│ ├── server.service
|
||||||
│ ├── cli.service
|
│ ├── cli.service
|
||||||
@@ -322,6 +336,8 @@ journalctl -u scheduler -f # 调度器实时日志
|
|||||||
└── cron_*.log # 定时任务专用日志
|
└── cron_*.log # 定时任务专用日志
|
||||||
```
|
```
|
||||||
|
|
||||||
|
CI 会将 `server`、`cli`、`scheduler` 和 `initializer` 打包为 `deployment.tar.gz` 后上传。部署目录由工作流顶部的 `DEPLOY_DIR` 统一配置(默认 `/root`)。压缩包上传完成后,服务器依次执行 `stop.sh`、覆盖解压、添加执行权限和 `start.sh`;启停脚本会读取 `.service-prefix` 操作带项目前缀的三个服务。首次部署前需要先运行一次 `initializer`。
|
||||||
|
|
||||||
## 内置功能清单
|
## 内置功能清单
|
||||||
|
|
||||||
- Hertz HTTP 框架 + 路由分组
|
- Hertz HTTP 框架 + 路由分组
|
||||||
|
|||||||
+169
@@ -0,0 +1,169 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"embed"
|
||||||
|
"errors"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// deploymentAssets contains everything needed to register and manage the services.
|
||||||
|
//
|
||||||
|
//go:embed .env.example install.sh start.sh stop.sh restart.sh deploy/*.service
|
||||||
|
var deploymentAssets embed.FS
|
||||||
|
|
||||||
|
var validServicePrefix = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9_.-]*$`)
|
||||||
|
|
||||||
|
var assetModes = map[string]os.FileMode{
|
||||||
|
"install.sh": 0o755,
|
||||||
|
"start.sh": 0o755,
|
||||||
|
"stop.sh": 0o755,
|
||||||
|
"restart.sh": 0o755,
|
||||||
|
".env.example": 0o644,
|
||||||
|
"deploy/server.service": 0o644,
|
||||||
|
"deploy/cli.service": 0o644,
|
||||||
|
"deploy/scheduler.service": 0o644,
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
prefixFlag := flag.String("prefix", "", "systemd 服务名前缀;未指定时交互输入")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
if runtime.GOOS != "linux" {
|
||||||
|
fatalf("初始化工具只能在 Linux 服务器上运行")
|
||||||
|
}
|
||||||
|
if err := requireRoot(); err != nil {
|
||||||
|
fatalf("%v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
appDir, err := executableDir()
|
||||||
|
if err != nil {
|
||||||
|
fatalf("获取程序目录失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
prefix, err := resolvePrefix(*prefixFlag, os.Stdin, os.Stdout)
|
||||||
|
if err != nil {
|
||||||
|
fatalf("读取项目前缀失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := extractAssets(appDir); err != nil {
|
||||||
|
fatalf("释放部署文件失败: %v", err)
|
||||||
|
}
|
||||||
|
envCreated, err := initializeEnv(appDir)
|
||||||
|
if err != nil {
|
||||||
|
fatalf("初始化 .env 失败: %v", err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(appDir, ".service-prefix"), []byte(prefix+"\n"), 0o644); err != nil {
|
||||||
|
fatalf("保存项目前缀失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Printf("部署文件已释放到 %s\n", appDir)
|
||||||
|
if envCreated {
|
||||||
|
fmt.Println("已根据 .env.example 初始化 .env,请按实际环境修改配置")
|
||||||
|
} else {
|
||||||
|
fmt.Println("检测到已有 .env,已保留现有配置")
|
||||||
|
}
|
||||||
|
fmt.Printf("正在注册服务: %s-server, %s-cli, %s-scheduler\n", prefix, prefix, prefix)
|
||||||
|
|
||||||
|
install := exec.Command("bash", filepath.Join(appDir, "install.sh"))
|
||||||
|
install.Dir = appDir
|
||||||
|
install.Stdin = os.Stdin
|
||||||
|
install.Stdout = os.Stdout
|
||||||
|
install.Stderr = os.Stderr
|
||||||
|
if err := install.Run(); err != nil {
|
||||||
|
fatalf("install.sh 执行失败: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func requireRoot() error {
|
||||||
|
uid, err := exec.Command("id", "-u").Output()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("无法检查当前用户权限: %w", err)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(string(uid)) != "0" {
|
||||||
|
return errors.New("请使用 root 权限运行: sudo ./initializer")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func executableDir() (string, error) {
|
||||||
|
filename, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
filename, err = filepath.Abs(filename)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Dir(filename), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolvePrefix(value string, in io.Reader, out io.Writer) (string, error) {
|
||||||
|
prefix := strings.TrimSpace(value)
|
||||||
|
if prefix == "" {
|
||||||
|
if _, err := fmt.Fprint(out, "请输入项目前缀(例如 myapp): "); err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
line, err := bufio.NewReader(in).ReadString('\n')
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
prefix = strings.TrimSpace(line)
|
||||||
|
}
|
||||||
|
|
||||||
|
if !validServicePrefix.MatchString(prefix) {
|
||||||
|
return "", fmt.Errorf("%q 不是有效前缀,只允许字母、数字、点、下划线和连字符,且必须以字母或数字开头", prefix)
|
||||||
|
}
|
||||||
|
return prefix, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func extractAssets(appDir string) error {
|
||||||
|
for name, mode := range assetModes {
|
||||||
|
content, err := deploymentAssets.ReadFile(name)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("读取 %s: %w", name, err)
|
||||||
|
}
|
||||||
|
target := filepath.Join(appDir, filepath.FromSlash(name))
|
||||||
|
if err := os.MkdirAll(filepath.Dir(target), 0o755); err != nil {
|
||||||
|
return fmt.Errorf("创建 %s: %w", filepath.Dir(target), err)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(target, content, mode); err != nil {
|
||||||
|
return fmt.Errorf("写入 %s: %w", target, err)
|
||||||
|
}
|
||||||
|
if err := os.Chmod(target, mode); err != nil {
|
||||||
|
return fmt.Errorf("设置 %s 权限: %w", target, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func initializeEnv(appDir string) (bool, error) {
|
||||||
|
target := filepath.Join(appDir, ".env")
|
||||||
|
if _, err := os.Stat(target); err == nil {
|
||||||
|
return false, nil
|
||||||
|
} else if !errors.Is(err, os.ErrNotExist) {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
|
||||||
|
content, err := deploymentAssets.ReadFile(".env.example")
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(target, content, 0o600); err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func fatalf(format string, args ...any) {
|
||||||
|
fmt.Fprintf(os.Stderr, "错误: "+format+"\n", args...)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
@@ -0,0 +1,87 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestResolvePrefixFromFlag(t *testing.T) {
|
||||||
|
prefix, err := resolvePrefix(" demo-api ", strings.NewReader(""), &bytes.Buffer{})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("resolvePrefix() error = %v", err)
|
||||||
|
}
|
||||||
|
if prefix != "demo-api" {
|
||||||
|
t.Fatalf("resolvePrefix() = %q, want %q", prefix, "demo-api")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitializeEnvCreatesAndPreservesConfig(t *testing.T) {
|
||||||
|
dir := t.TempDir()
|
||||||
|
created, err := initializeEnv(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("initializeEnv() error = %v", err)
|
||||||
|
}
|
||||||
|
if !created {
|
||||||
|
t.Fatal("initializeEnv() created = false, want true")
|
||||||
|
}
|
||||||
|
|
||||||
|
target := filepath.Join(dir, ".env")
|
||||||
|
want, err := deploymentAssets.ReadFile(".env.example")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read embedded .env.example: %v", err)
|
||||||
|
}
|
||||||
|
got, err := os.ReadFile(target)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read generated .env: %v", err)
|
||||||
|
}
|
||||||
|
if !bytes.Equal(got, want) {
|
||||||
|
t.Fatal("generated .env does not match embedded .env.example")
|
||||||
|
}
|
||||||
|
|
||||||
|
const custom = "SECRET=keep-me\n"
|
||||||
|
if err := os.WriteFile(target, []byte(custom), 0o600); err != nil {
|
||||||
|
t.Fatalf("write existing .env: %v", err)
|
||||||
|
}
|
||||||
|
created, err = initializeEnv(dir)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("second initializeEnv() error = %v", err)
|
||||||
|
}
|
||||||
|
if created {
|
||||||
|
t.Fatal("second initializeEnv() created = true, want false")
|
||||||
|
}
|
||||||
|
got, err = os.ReadFile(target)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("read preserved .env: %v", err)
|
||||||
|
}
|
||||||
|
if string(got) != custom {
|
||||||
|
t.Fatalf("existing .env was overwritten: got %q", got)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolvePrefixFromInput(t *testing.T) {
|
||||||
|
var output bytes.Buffer
|
||||||
|
prefix, err := resolvePrefix("", strings.NewReader("template_1\n"), &output)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("resolvePrefix() error = %v", err)
|
||||||
|
}
|
||||||
|
if prefix != "template_1" {
|
||||||
|
t.Fatalf("resolvePrefix() = %q, want %q", prefix, "template_1")
|
||||||
|
}
|
||||||
|
if !strings.Contains(output.String(), "请输入项目前缀") {
|
||||||
|
t.Fatalf("prompt = %q, want prefix prompt", output.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResolvePrefixRejectsUnsafeValues(t *testing.T) {
|
||||||
|
values := []string{"", "-demo", "demo api", "demo;reboot", "项目"}
|
||||||
|
for _, value := range values {
|
||||||
|
t.Run(value, func(t *testing.T) {
|
||||||
|
if _, err := resolvePrefix(value, strings.NewReader(""), &bytes.Buffer{}); err == nil {
|
||||||
|
t.Fatalf("resolvePrefix(%q) succeeded, want error", value)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
+33
-16
@@ -1,11 +1,23 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -euo pipefail
|
||||||
|
|
||||||
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
|
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
DEPLOY_DIR="$APP_DIR/deploy"
|
DEPLOY_DIR="$APP_DIR/deploy"
|
||||||
SERVICE_DIR="/etc/systemd/system"
|
SERVICE_DIR="/etc/systemd/system"
|
||||||
LOG_DIR="$APP_DIR/logs"
|
LOG_DIR="$APP_DIR/logs"
|
||||||
SERVICES="server cli scheduler"
|
PREFIX_FILE="$APP_DIR/.service-prefix"
|
||||||
|
SERVICE_SUFFIXES=(server cli scheduler)
|
||||||
|
|
||||||
|
if [ ! -f "$PREFIX_FILE" ]; then
|
||||||
|
echo "缺少 $PREFIX_FILE,请先运行 initializer 完成初始化"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
SERVICE_PREFIX="$(tr -d '\r\n' < "$PREFIX_FILE")"
|
||||||
|
if [[ ! "$SERVICE_PREFIX" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]]; then
|
||||||
|
echo "无效的服务前缀: $SERVICE_PREFIX"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
if [ "$(id -u)" -ne 0 ]; then
|
if [ "$(id -u)" -ne 0 ]; then
|
||||||
echo "请使用 root 权限运行: sudo bash install.sh"
|
echo "请使用 root 权限运行: sudo bash install.sh"
|
||||||
@@ -16,27 +28,32 @@ mkdir -p "$LOG_DIR"
|
|||||||
|
|
||||||
echo "=== 安装 systemd 服务 ==="
|
echo "=== 安装 systemd 服务 ==="
|
||||||
|
|
||||||
for svc in $SERVICES; do
|
for svc in "${SERVICE_SUFFIXES[@]}"; do
|
||||||
src="$DEPLOY_DIR/${svc}.service"
|
src="$DEPLOY_DIR/${svc}.service"
|
||||||
|
service_name="${SERVICE_PREFIX}-${svc}"
|
||||||
if [ ! -f "$src" ]; then
|
if [ ! -f "$src" ]; then
|
||||||
echo "[${svc}] service 文件不存在: $src"
|
echo "[${svc}] service 文件不存在: $src"
|
||||||
continue
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
sed "s|WorkingDirectory=/root|WorkingDirectory=$APP_DIR|g; \
|
escaped_app_dir="$(printf '%s' "$APP_DIR" | sed 's/[&|\\]/\\&/g')"
|
||||||
s|ExecStart=/root/|ExecStart=$APP_DIR/|g; \
|
sed "s|/root|$escaped_app_dir|g" "$src" > "$SERVICE_DIR/${service_name}.service"
|
||||||
s|EnvironmentFile=-/root/.env|EnvironmentFile=-$APP_DIR/.env|g; \
|
|
||||||
s|/root/logs/|$LOG_DIR/|g" \
|
|
||||||
"$src" > "$SERVICE_DIR/${svc}.service"
|
|
||||||
|
|
||||||
echo "[${svc}] 已安装到 $SERVICE_DIR/${svc}.service"
|
echo "[${service_name}] 已安装到 $SERVICE_DIR/${service_name}.service"
|
||||||
done
|
done
|
||||||
|
|
||||||
systemctl daemon-reload
|
systemctl daemon-reload
|
||||||
|
|
||||||
for svc in $SERVICES; do
|
for svc in "${SERVICE_SUFFIXES[@]}"; do
|
||||||
systemctl enable "$svc"
|
service_name="${SERVICE_PREFIX}-${svc}"
|
||||||
echo "[${svc}] 已设置开机自启"
|
systemctl enable "$service_name"
|
||||||
|
echo "[${service_name}] 已设置开机自启"
|
||||||
|
done
|
||||||
|
|
||||||
|
for binary in server cli scheduler; do
|
||||||
|
if [ -f "$APP_DIR/$binary" ]; then
|
||||||
|
chmod +x "$APP_DIR/$binary"
|
||||||
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
@@ -45,6 +62,6 @@ echo "使用方式:"
|
|||||||
echo " bash start.sh # 启动全部"
|
echo " bash start.sh # 启动全部"
|
||||||
echo " bash stop.sh # 停止全部"
|
echo " bash stop.sh # 停止全部"
|
||||||
echo " bash restart.sh # 重启全部"
|
echo " bash restart.sh # 重启全部"
|
||||||
echo " systemctl status server # 查看主服务状态"
|
echo " systemctl status ${SERVICE_PREFIX}-server # 查看主服务状态"
|
||||||
echo " systemctl status scheduler # 查看调度器状态"
|
echo " systemctl status ${SERVICE_PREFIX}-scheduler # 查看调度器状态"
|
||||||
echo " journalctl -u scheduler -f # 查看调度器实时日志"
|
echo " journalctl -u ${SERVICE_PREFIX}-scheduler -f # 查看调度器实时日志"
|
||||||
|
|||||||
+10
-3
@@ -1,10 +1,17 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
SERVICES="server cli scheduler"
|
set -euo pipefail
|
||||||
|
|
||||||
for svc in $SERVICES; do
|
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PREFIX_FILE="$APP_DIR/.service-prefix"
|
||||||
|
[ -f "$PREFIX_FILE" ] || { echo "缺少 $PREFIX_FILE,请先运行 initializer"; exit 1; }
|
||||||
|
SERVICE_PREFIX="$(tr -d '\r\n' < "$PREFIX_FILE")"
|
||||||
|
[[ "$SERVICE_PREFIX" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]] || { echo "无效的服务前缀: $SERVICE_PREFIX"; exit 1; }
|
||||||
|
SERVICES=("${SERVICE_PREFIX}-server" "${SERVICE_PREFIX}-cli" "${SERVICE_PREFIX}-scheduler")
|
||||||
|
|
||||||
|
for svc in "${SERVICES[@]}"; do
|
||||||
systemctl restart "$svc"
|
systemctl restart "$svc"
|
||||||
echo "[$svc] 已重启"
|
echo "[$svc] 已重启"
|
||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
systemctl status $SERVICES --no-pager -l
|
systemctl status "${SERVICES[@]}" --no-pager -l
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
SERVICES="server cli scheduler"
|
set -euo pipefail
|
||||||
|
|
||||||
for svc in $SERVICES; do
|
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PREFIX_FILE="$APP_DIR/.service-prefix"
|
||||||
|
[ -f "$PREFIX_FILE" ] || { echo "缺少 $PREFIX_FILE,请先运行 initializer"; exit 1; }
|
||||||
|
SERVICE_PREFIX="$(tr -d '\r\n' < "$PREFIX_FILE")"
|
||||||
|
[[ "$SERVICE_PREFIX" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]] || { echo "无效的服务前缀: $SERVICE_PREFIX"; exit 1; }
|
||||||
|
SERVICES=("${SERVICE_PREFIX}-server" "${SERVICE_PREFIX}-cli" "${SERVICE_PREFIX}-scheduler")
|
||||||
|
|
||||||
|
for svc in "${SERVICES[@]}"; do
|
||||||
if systemctl is-active --quiet "$svc"; then
|
if systemctl is-active --quiet "$svc"; then
|
||||||
echo "[$svc] 已在运行"
|
echo "[$svc] 已在运行"
|
||||||
else
|
else
|
||||||
@@ -11,4 +18,4 @@ for svc in $SERVICES; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
systemctl status $SERVICES --no-pager -l
|
systemctl status "${SERVICES[@]}" --no-pager -l
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
SERVICES="server cli scheduler"
|
set -euo pipefail
|
||||||
|
|
||||||
for svc in $SERVICES; do
|
APP_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
PREFIX_FILE="$APP_DIR/.service-prefix"
|
||||||
|
[ -f "$PREFIX_FILE" ] || { echo "缺少 $PREFIX_FILE,请先运行 initializer"; exit 1; }
|
||||||
|
SERVICE_PREFIX="$(tr -d '\r\n' < "$PREFIX_FILE")"
|
||||||
|
[[ "$SERVICE_PREFIX" =~ ^[A-Za-z0-9][A-Za-z0-9_.-]*$ ]] || { echo "无效的服务前缀: $SERVICE_PREFIX"; exit 1; }
|
||||||
|
SERVICES=("${SERVICE_PREFIX}-server" "${SERVICE_PREFIX}-cli" "${SERVICE_PREFIX}-scheduler")
|
||||||
|
|
||||||
|
for svc in "${SERVICES[@]}"; do
|
||||||
if systemctl is-active --quiet "$svc"; then
|
if systemctl is-active --quiet "$svc"; then
|
||||||
systemctl stop "$svc"
|
systemctl stop "$svc"
|
||||||
echo "[$svc] 已停止"
|
echo "[$svc] 已停止"
|
||||||
|
|||||||
Reference in New Issue
Block a user