物联网软件
This commit is contained in:
@@ -1,20 +1,105 @@
|
||||
package config
|
||||
|
||||
// 变动
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// 常量
|
||||
const (
|
||||
// 版本号
|
||||
APP_VERSION = 1
|
||||
APP_VERSION = 26
|
||||
|
||||
BASE_URL = "https://ai.ronsunny.cn:8090"
|
||||
//BASE_URL = "http://127.0.0.1:13011"
|
||||
Log_file_dic = "./logs"
|
||||
MQTT_BROKER = "tls://ai.ronsunny.cn:8093"
|
||||
PASSWORD = "123456"
|
||||
//BASE_URL = "https://ai.ronsunny.cn:8090"
|
||||
BASE_URL = "http://127.0.0.1:13011"
|
||||
LOG_FILE_DIC = "./logs"
|
||||
LOG_CHECK_INTERVAL_HOURS = 10 // 文件轮询检查间隔(秒)
|
||||
MQTT_BROKER = "tls://ai.ronsunny.cn:8093"
|
||||
PASSWORD = "123456"
|
||||
|
||||
DOCKER_REGISTRY = "ai.ronsunny.cn:13011"
|
||||
DOCKER_USERNAME = "iot_device"
|
||||
DOCKER_PASSWORD = "Bbit000000"
|
||||
DOCKER_IMAGE = "ai.ronsunny.cn:13011/bbit_iot/ce_sentinel"
|
||||
DOCKER_CONTAINER_NAME = "BBIT_Project"
|
||||
DOCKER_TIME_OUT = 30 * time.Second
|
||||
|
||||
DAEMON_UPDATE_CHECK_INTERVAL_SECONDS = 5 // 文件轮询检查间隔(秒)
|
||||
DAEMON_ALIVE_GAP_SECONDS = 30
|
||||
)
|
||||
|
||||
var (
|
||||
// DeviceType string
|
||||
// DeptId string
|
||||
DOCKER_CONTAINER_BINDS = []string{
|
||||
"/opt/sentinel/logs:/app/logs:rw",
|
||||
}
|
||||
)
|
||||
|
||||
type AppConfig struct {
|
||||
VersionCode int `yaml:"version_code"`
|
||||
PID int `yaml:"pid"`
|
||||
NeedUpdate bool `yaml:"need_update"`
|
||||
/**
|
||||
状态 语义
|
||||
CONNECTED MQTT 已连,设备可控
|
||||
DEGRADED MQTT 短暂失联,容忍期
|
||||
CONTROL_LOST MQTT 长期失联,不应继续运行
|
||||
*/
|
||||
ControlState string `yaml:"control_state"` // CONNECTED / DEGRADED / CONTROL_LOST
|
||||
LastAliveAt int64 `yaml:"last_alive_at"` // Unix 秒
|
||||
}
|
||||
|
||||
func LoadConfig() *AppConfig {
|
||||
cfg := &AppConfig{}
|
||||
if err := loadLocalConfig(cfg); err != nil {
|
||||
print("[daemon] 加载配置失败,当作新状态处理:", err)
|
||||
}
|
||||
return cfg
|
||||
}
|
||||
|
||||
func loadLocalConfig[T any](cfg *T) error {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Dir(exePath)
|
||||
cfgPath := filepath.Join(dir, "config.yaml")
|
||||
|
||||
// 不存在就创建一个默认的
|
||||
if _, err := os.Stat(cfgPath); os.IsNotExist(err) {
|
||||
cfg := &AppConfig{
|
||||
VersionCode: -1,
|
||||
PID: -1,
|
||||
NeedUpdate: false,
|
||||
}
|
||||
return WriteLocalConfig(cfg)
|
||||
}
|
||||
|
||||
data, err := os.ReadFile(cfgPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := yaml.Unmarshal(data, cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
func WriteLocalConfig(cfg any) error {
|
||||
exePath, err := os.Executable()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dir := filepath.Dir(exePath)
|
||||
cfgPath := filepath.Join(dir, "config.yaml")
|
||||
|
||||
data, err := yaml.Marshal(cfg)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return os.WriteFile(cfgPath, data, 0644)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,165 @@
|
||||
package docker
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"sentinel/pkg/config"
|
||||
"time"
|
||||
|
||||
"sentinel/pkg/log"
|
||||
|
||||
"github.com/docker/docker/api/types/container"
|
||||
"github.com/docker/docker/api/types/filters"
|
||||
"github.com/docker/docker/api/types/image"
|
||||
"github.com/docker/docker/api/types/registry"
|
||||
"github.com/docker/docker/client"
|
||||
)
|
||||
|
||||
type DockerManager struct {
|
||||
registry string
|
||||
username string
|
||||
password string
|
||||
cli *client.Client
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func NewDockerManager() (*DockerManager, error) {
|
||||
cli, err := client.NewClientWithOpts(
|
||||
client.FromEnv,
|
||||
client.WithAPIVersionNegotiation(),
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &DockerManager{
|
||||
cli: cli,
|
||||
registry: config.DOCKER_REGISTRY,
|
||||
username: config.DOCKER_USERNAME,
|
||||
password: config.DOCKER_PASSWORD,
|
||||
timeout: config.DOCKER_TIME_OUT,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *DockerManager) PullImage(ctx context.Context, refStr string) error {
|
||||
auth := registry.AuthConfig{
|
||||
ServerAddress: d.registry,
|
||||
Username: d.username,
|
||||
Password: d.password,
|
||||
}
|
||||
authBytes, _ := json.Marshal(auth)
|
||||
authStr := base64.StdEncoding.EncodeToString(authBytes)
|
||||
|
||||
reader, err := d.cli.ImagePull(ctx, refStr, image.PullOptions{
|
||||
RegistryAuth: authStr,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer reader.Close()
|
||||
// 消费输出,避免挂起
|
||||
_, _ = io.Copy(io.Discard, reader)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerManager) RunContainer(ctx context.Context) error {
|
||||
// 停止并删除已有容器
|
||||
if err := d.StopAndRemoveContainer(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建容器
|
||||
log.Println("正在启动名为<", config.DOCKER_CONTAINER_NAME, ">的容器")
|
||||
resp, err := d.cli.ContainerCreate(
|
||||
ctx,
|
||||
&container.Config{
|
||||
Image: config.DOCKER_IMAGE,
|
||||
Healthcheck: &container.HealthConfig{
|
||||
Test: []string{"CMD-SHELL", "echo ok"},
|
||||
Interval: 5 * time.Second,
|
||||
Timeout: 2 * time.Second,
|
||||
Retries: 3,
|
||||
},
|
||||
},
|
||||
&container.HostConfig{
|
||||
Binds: config.DOCKER_CONTAINER_BINDS,
|
||||
NetworkMode: "host", // <-- 使用宿主机网络
|
||||
},
|
||||
nil, // NetworkConfig
|
||||
nil, // Platform
|
||||
config.DOCKER_CONTAINER_NAME,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 启动容器
|
||||
if err := d.cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil {
|
||||
return err
|
||||
}
|
||||
log.Println("容器已成功运行")
|
||||
return nil
|
||||
}
|
||||
|
||||
// StopAndRemoveContainer 停止并删除指定容器
|
||||
func (d *DockerManager) StopAndRemoveContainer(ctx context.Context) error {
|
||||
|
||||
containers, err := d.cli.ContainerList(ctx, container.ListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(
|
||||
filters.KeyValuePair{Key: "name", Value: config.DOCKER_CONTAINER_NAME},
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, c := range containers {
|
||||
log.Println("正在停止名为<", config.DOCKER_CONTAINER_NAME, ">的容器:", c.ID)
|
||||
timeout := 10 * time.Second
|
||||
seconds := int(timeout.Seconds())
|
||||
if err := d.cli.ContainerStop(ctx, c.ID, container.StopOptions{
|
||||
Timeout: &seconds,
|
||||
}); err != nil {
|
||||
log.Println("Failed to stop container %s: %v", c.ID, err)
|
||||
return err
|
||||
}
|
||||
if err := d.cli.ContainerRemove(ctx, c.ID, container.RemoveOptions{}); err != nil {
|
||||
log.Println("Failed to remove container %s: %v", c.ID, err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerManager) CheckContainerHealth(ctx context.Context, containerName string) (string, error) {
|
||||
if containerName == "" {
|
||||
return "", errors.New("containerName must not be empty")
|
||||
}
|
||||
containers, err := d.cli.ContainerList(ctx, container.ListOptions{
|
||||
All: true,
|
||||
Filters: filters.NewArgs(
|
||||
filters.KeyValuePair{Key: "name", Value: containerName},
|
||||
),
|
||||
})
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(containers) == 0 {
|
||||
return "", errors.New("container not found")
|
||||
}
|
||||
|
||||
inspect, err := d.cli.ContainerInspect(ctx, containers[0].ID)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if inspect.State.Health != nil {
|
||||
return inspect.State.Health.Status, nil
|
||||
}
|
||||
return "unknown", nil
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sentinel/pkg/config"
|
||||
"time"
|
||||
)
|
||||
|
||||
@@ -17,7 +18,14 @@ func Init(dir string) {
|
||||
if err := os.MkdirAll(logDir, 0755); err != nil {
|
||||
fmt.Println("create log dir failed:", err)
|
||||
}
|
||||
cleanupOldLogs()
|
||||
go func() {
|
||||
ticker := time.NewTicker(config.LOG_CHECK_INTERVAL_HOURS * time.Hour)
|
||||
defer ticker.Stop()
|
||||
for range ticker.C {
|
||||
cleanupOldLogs()
|
||||
}
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
// Cleanup 删除超过7天的日志文件
|
||||
@@ -73,6 +81,11 @@ func Println(v ...interface{}) {
|
||||
msg := fmt.Sprint(v...)
|
||||
logToFile("INFO", msg)
|
||||
}
|
||||
|
||||
func Debug(v ...interface{}) {
|
||||
//msg := fmt.Sprint(v...)
|
||||
//logToFile("DEBUF", msg)
|
||||
}
|
||||
func Warn(msg string) {
|
||||
logToFile("WARN", msg)
|
||||
}
|
||||
|
||||
@@ -8,4 +8,8 @@ type MainProgramStarter interface {
|
||||
Start(targetExe string) error
|
||||
|
||||
GetMainName() string
|
||||
|
||||
IsProcessRunning(pid int) (bool, error)
|
||||
|
||||
KillProcess(pid int) error
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
@@ -98,3 +99,19 @@ func GetDisk() string {
|
||||
}
|
||||
return fmt.Sprintf("%dGB", usage.Total/1024/1024/1024)
|
||||
}
|
||||
|
||||
func PayloadToMap(payload []byte) (map[string]interface{}, error) {
|
||||
var result map[string]interface{}
|
||||
err := json.Unmarshal(payload, &result)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func JSONToString(data map[string]interface{}) string {
|
||||
jsonBytes, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
return string(jsonBytes)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user