feat: proxmox idlewatcher (#88)

* feat: idle sleep for proxmox LXCs

* refactor: replace deprecated docker api types

* chore(api): remove debug task list endpoint

* refactor: move servemux to gphttp/servemux; favicon.go to v1/favicon

* refactor: introduce Pool interface, move agent_pool to agent module

* refactor: simplify api code

* feat: introduce debug api

* refactor: remove net.URL and net.CIDR types, improved unmarshal handling

* chore: update Makefile for debug build tag, update README

* chore: add gperr.Unwrap method

* feat: relative time and duration formatting

* chore: add ROOT_DIR environment variable, refactor

* migration: move homepage override and icon cache to $BASE_DIR/data, add migration code

* fix: nil dereference on marshalling service health

* fix: wait for route deletion

* chore: enhance tasks debuggability

* feat: stdout access logger and MultiWriter

* fix(agent): remove agent properly on verify error

* fix(metrics): disk exclusion logic and added corresponding tests

* chore: update schema and prettify, fix package.json and Makefile

* fix: I/O buffer not being shrunk before putting back to pool

* feat: enhanced error handling module

* chore: deps upgrade

* feat: better value formatting and handling

---------

Co-authored-by: yusing <yusing@6uo.me>
This commit is contained in:
Yuzerion
2025-04-16 14:52:33 +08:00
committed by GitHub
parent 88f3a95b61
commit 57292f0fe8
173 changed files with 4131 additions and 2096 deletions

View File

@@ -19,7 +19,7 @@ func NewConfigFileWatcher(filename string) Watcher {
if configDirWatcher == nil {
t := task.RootTask("config_dir_watcher", false)
configDirWatcher = NewDirectoryWatcher(t, common.ConfigBasePath)
configDirWatcher = NewDirectoryWatcher(t, common.ConfigDir)
}
return configDirWatcher.Add(filename)
}

View File

@@ -36,8 +36,8 @@ const (
ActionForceReload
actionContainerWakeMask = ActionContainerCreate | ActionContainerStart | ActionContainerUnpause
actionContainerSleepMask = ActionContainerKill | ActionContainerStop | ActionContainerPause | ActionContainerDie
actionContainerStartMask = ActionContainerCreate | ActionContainerStart | ActionContainerUnpause
actionContainerStopMask = ActionContainerKill | ActionContainerStop | ActionContainerDie
)
const (
@@ -83,10 +83,14 @@ func (a Action) String() string {
return actionNameMap[a]
}
func (a Action) IsContainerWake() bool {
return a&actionContainerWakeMask != 0
func (a Action) IsContainerStart() bool {
return a&actionContainerStartMask != 0
}
func (a Action) IsContainerSleep() bool {
return a&actionContainerSleepMask != 0
func (a Action) IsContainerStop() bool {
return a&actionContainerStopMask != 0
}
func (a Action) IsContainerPause() bool {
return a == ActionContainerPause
}

View File

@@ -1,34 +1,35 @@
package monitor
package health
import (
"encoding/json"
"net/url"
"strconv"
"time"
net "github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/utils/strutils"
"github.com/yusing/go-proxy/internal/watcher/health"
)
type JSONRepresentation struct {
Name string
Config *health.HealthCheckConfig
Status health.Status
Config *HealthCheckConfig
Status Status
Started time.Time
Uptime time.Duration
Latency time.Duration
LastSeen time.Time
Detail string
URL *net.URL
URL *url.URL
Extra map[string]any
}
func (jsonRepr *JSONRepresentation) MarshalJSON() ([]byte, error) {
url := jsonRepr.URL.String()
func (jsonRepr *JSONRepresentation) MarshalMap() map[string]any {
var url string
if jsonRepr.URL != nil {
url = jsonRepr.URL.String()
}
if url == "http://:0" {
url = ""
}
return json.Marshal(map[string]any{
return map[string]any{
"name": jsonRepr.Name,
"config": jsonRepr.Config,
"started": jsonRepr.Started.Unix(),
@@ -43,5 +44,5 @@ func (jsonRepr *JSONRepresentation) MarshalJSON() ([]byte, error) {
"detail": jsonRepr.Detail,
"url": url,
"extra": jsonRepr.Extra,
})
}
}

View File

@@ -7,7 +7,6 @@ import (
"net/url"
agentPkg "github.com/yusing/go-proxy/agent/pkg/agent"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/watcher/health"
)
@@ -24,7 +23,7 @@ type (
}
)
func AgentTargetFromURL(url *types.URL) *AgentCheckHealthTarget {
func AgentTargetFromURL(url *url.URL) *AgentCheckHealthTarget {
return &AgentCheckHealthTarget{
Scheme: url.Scheme,
Host: url.Host,
@@ -40,12 +39,12 @@ func (target *AgentCheckHealthTarget) buildQuery() string {
return query.Encode()
}
func (target *AgentCheckHealthTarget) displayURL() *types.URL {
return types.NewURL(&url.URL{
func (target *AgentCheckHealthTarget) displayURL() *url.URL {
return &url.URL{
Scheme: target.Scheme,
Host: target.Host,
Path: target.Path,
})
}
}
func NewAgentProxiedMonitor(agent *agentPkg.AgentConfig, config *health.HealthCheckConfig, target *AgentCheckHealthTarget) *AgentProxiedMonitor {

View File

@@ -1,9 +1,9 @@
package monitor
import (
"github.com/docker/docker/api/types/container"
"github.com/yusing/go-proxy/internal/docker"
dockerTypes "github.com/docker/docker/api/types"
"github.com/yusing/go-proxy/internal/watcher/health"
)
@@ -48,7 +48,7 @@ func (mon *DockerHealthMonitor) CheckHealth() (result *health.HealthCheckResult,
return mon.fallback.CheckHealth()
}
result = new(health.HealthCheckResult)
result.Healthy = cont.State.Health.Status == dockerTypes.Healthy
result.Healthy = cont.State.Health.Status == container.Healthy
if len(cont.State.Health.Log) > 0 {
lastLog := cont.State.Health.Log[len(cont.State.Health.Log)-1]
result.Detail = lastLog.Output

View File

@@ -4,9 +4,9 @@ import (
"crypto/tls"
"errors"
"net/http"
"net/url"
"time"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/watcher/health"
"github.com/yusing/go-proxy/pkg"
)
@@ -26,7 +26,7 @@ var pinger = &http.Client{
},
}
func NewHTTPHealthMonitor(url *types.URL, config *health.HealthCheckConfig) *HTTPHealthMonitor {
func NewHTTPHealthMonitor(url *url.URL, config *health.HealthCheckConfig) *HTTPHealthMonitor {
mon := new(HTTPHealthMonitor)
mon.monitor = newMonitor(url, config, mon.CheckHealth)
if config.UseGet {
@@ -53,7 +53,7 @@ func (mon *HTTPHealthMonitor) CheckHealth() (result *health.HealthCheckResult, e
}
req.Close = true
req.Header.Set("Connection", "close")
req.Header.Set("User-Agent", "GoDoxy/"+pkg.GetVersion())
req.Header.Set("User-Agent", "GoDoxy/"+pkg.GetVersion().String())
start := time.Now()
resp, respErr := pinger.Do(req)

View File

@@ -4,12 +4,12 @@ import (
"context"
"errors"
"fmt"
"net/url"
"time"
"github.com/yusing/go-proxy/internal/docker"
"github.com/yusing/go-proxy/internal/gperr"
"github.com/yusing/go-proxy/internal/logging"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/notif"
route "github.com/yusing/go-proxy/internal/route/types"
"github.com/yusing/go-proxy/internal/task"
@@ -23,7 +23,7 @@ type (
monitor struct {
service string
config *health.HealthCheckConfig
url atomic.Value[*types.URL]
url atomic.Value[*url.URL]
status atomic.Value[health.Status]
lastResult atomic.Value[*health.HealthCheckResult]
@@ -52,7 +52,7 @@ func NewMonitor(r route.Route) health.HealthMonCheck {
}
}
if r.IsDocker() {
cont := r.DockerContainer()
cont := r.ContainerInfo()
client, err := docker.NewClient(cont.DockerHost)
if err != nil {
return mon
@@ -63,7 +63,7 @@ func NewMonitor(r route.Route) health.HealthMonCheck {
return mon
}
func newMonitor(url *types.URL, config *health.HealthCheckConfig, healthCheckFunc HealthCheckFunc) *monitor {
func newMonitor(url *url.URL, config *health.HealthCheckConfig, healthCheckFunc HealthCheckFunc) *monitor {
mon := &monitor{
config: config,
checkHealth: healthCheckFunc,
@@ -135,12 +135,12 @@ func (mon *monitor) Finish(reason any) {
}
// UpdateURL implements HealthChecker.
func (mon *monitor) UpdateURL(url *types.URL) {
func (mon *monitor) UpdateURL(url *url.URL) {
mon.url.Store(url)
}
// URL implements HealthChecker.
func (mon *monitor) URL() *types.URL {
func (mon *monitor) URL() *url.URL {
return mon.url.Load()
}
@@ -179,8 +179,8 @@ func (mon *monitor) String() string {
return mon.Name()
}
// MarshalJSON implements json.Marshaler of HealthMonitor.
func (mon *monitor) MarshalJSON() ([]byte, error) {
// MarshalMap implements health.HealthMonitor.
func (mon *monitor) MarshalMap() map[string]any {
res := mon.lastResult.Load()
if res == nil {
res = &health.HealthCheckResult{
@@ -188,7 +188,7 @@ func (mon *monitor) MarshalJSON() ([]byte, error) {
}
}
return (&JSONRepresentation{
return (&health.JSONRepresentation{
Name: mon.service,
Config: mon.config,
Status: mon.status.Load(),
@@ -198,7 +198,7 @@ func (mon *monitor) MarshalJSON() ([]byte, error) {
LastSeen: GetLastSeen(mon.service),
Detail: res.Detail,
URL: mon.url.Load(),
}).MarshalJSON()
}).MarshalMap()
}
func (mon *monitor) checkUpdateHealth() error {

View File

@@ -2,9 +2,9 @@ package monitor
import (
"net"
"net/url"
"time"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/watcher/health"
)
@@ -15,7 +15,7 @@ type (
}
)
func NewRawHealthMonitor(url *types.URL, config *health.HealthCheckConfig) *RawHealthMonitor {
func NewRawHealthMonitor(url *url.URL, config *health.HealthCheckConfig) *RawHealthMonitor {
mon := new(RawHealthMonitor)
mon.monitor = newMonitor(url, config, mon.CheckHealth)
mon.dialer = &net.Dialer{

View File

@@ -1,12 +1,12 @@
package health
import (
"encoding/json"
"fmt"
"net/url"
"time"
"github.com/yusing/go-proxy/internal/net/types"
"github.com/yusing/go-proxy/internal/task"
"github.com/yusing/go-proxy/internal/utils"
)
type (
@@ -24,15 +24,15 @@ type (
task.TaskStarter
task.TaskFinisher
fmt.Stringer
json.Marshaler
utils.MapMarshaler
WithHealthInfo
Name() string
}
HealthChecker interface {
CheckHealth() (result *HealthCheckResult, err error)
URL() *types.URL
URL() *url.URL
Config() *HealthCheckConfig
UpdateURL(url *types.URL)
UpdateURL(url *url.URL)
}
HealthMonCheck interface {
HealthMonitor