Skip to content

Commit 24f9036

Browse files
author
root
committed
add extensions
1 parent 8ce50a4 commit 24f9036

89 files changed

Lines changed: 5093 additions & 2733 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Note.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -154,4 +154,8 @@ crontab list 保存不住
154154

155155
去掉所有与 docker.sock 的强依赖与硬编码
156156

157-
平台metrics 的读取查询与对服务器 metrics 的查询,是不是后端代码应该分离
157+
平台metrics 的读取查询与对服务器 metrics 的查询,是不是后端代码应该分离
158+
159+
所以 appos 容器內其實完全可以通過 uname -r、/proc/meminfo 等獲取宿主機的內核版本、內存大小、CPU 信息,不需要任何特殊權限。只有 OS 發行版(os-release)是個例外
160+
161+

backend/docs/openapi/api.yaml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9735,15 +9735,13 @@ paths:
97359735
content:
97369736
application/json:
97379737
schema:
9738-
additionalProperties: true
9739-
type: object
9738+
$ref: '#/components/schemas/SuccessEnvelope'
97409739
description: OK
97419740
"401":
97429741
content:
97439742
application/json:
97449743
schema:
9745-
additionalProperties: true
9746-
type: object
9744+
$ref: '#/components/schemas/ErrorEnvelope'
97479745
description: Unauthorized
97489746
security:
97499747
- bearerAuth: []
@@ -10022,7 +10020,7 @@ paths:
1002210020
- Settings
1002310021
/api/software/local:
1002410022
get:
10025-
description: Returns AppOS-local software inventory derived from the local catalog and runtime checks.
10023+
description: Returns AppOS-local built-in component inventory derived from the local runtime registry, with software-catalog metadata attached when available.
1002610024
operationId: get_api_software_local
1002710025
responses:
1002810026
"200":

backend/docs/openapi/ext-api.yaml

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5045,15 +5045,13 @@ paths:
50455045
content:
50465046
application/json:
50475047
schema:
5048-
type: object
5049-
additionalProperties: true
5048+
$ref: '#/components/schemas/SuccessEnvelope'
50505049
"401":
50515050
description: Unauthorized
50525051
content:
50535052
application/json:
50545053
schema:
5055-
type: object
5056-
additionalProperties: true
5054+
$ref: '#/components/schemas/ErrorEnvelope'
50575055
/api/servers/local/docker-bridge:
50585056
get:
50595057
tags: [Servers]
@@ -8337,7 +8335,7 @@ paths:
83378335
get:
83388336
tags: [Software]
83398337
summary: List AppOS-local software components
8340-
description: "Returns AppOS-local software inventory derived from the local catalog and runtime checks."
8338+
description: "Returns AppOS-local built-in component inventory derived from the local runtime registry, with software-catalog metadata attached when available."
83418339
operationId: get_api_software_local
83428340
security:
83438341
- bearerAuth: []

backend/domain/config/sysconfig/schema/schema.go

Lines changed: 14 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -212,24 +212,22 @@ var entryCatalog = []EntrySchema{
212212
Module: "proxy",
213213
Key: "network",
214214
Fields: []FieldSchema{
215-
{ID: "httpProxy", Label: "HTTP Proxy", Type: "string"},
216-
{ID: "httpsProxy", Label: "HTTPS Proxy", Type: "string"},
217-
{ID: "noProxy", Label: "No Proxy", Type: "string"},
218-
{ID: "username", Label: "Username", Type: "string"},
219-
{ID: "password", Label: "Password", Type: "string", Sensitive: true},
215+
{ID: "enabled", Label: "Enable Proxy", Type: "boolean", HelpText: "Enable outbound proxy resolution for platform Docker operations."},
216+
{ID: "httpConnectorId", Label: "HTTP Proxy Connector", Type: "relation", HelpText: "Connector used for HTTP proxy traffic."},
217+
{ID: "httpsConnectorId", Label: "HTTPS Proxy Connector", Type: "relation", HelpText: "Connector used for HTTPS proxy traffic."},
220218
},
221219
},
222220
{
223-
ID: "docker-mirror",
224-
Title: "Docker Mirrors",
225-
Description: "Speed up AppOS image pulls. Does not change server Docker settings.",
226-
Section: SectionWorkspace,
227-
Source: SourceCustom,
228-
Module: "docker",
229-
Key: "mirror",
221+
ID: "docker-mirror",
222+
Title: "Docker Mirrors",
223+
Description: "Speed up AppOS image pulls. Does not change server Docker settings.",
224+
Section: SectionWorkspace,
225+
Source: SourceCustom,
226+
Module: "docker",
227+
Key: "mirror",
230228
Fields: []FieldSchema{
231-
{ID: "mirrors", Label: "Pull Sources", Type: "string-list"},
232-
{ID: "allowInsecureRegistries", Label: "Allow Insecure Registries", Type: "boolean"},
229+
{ID: "mirrors", Label: "Pull Sources", Type: "string-list"},
230+
{ID: "allowInsecureRegistries", Label: "Allow Insecure Registries", Type: "boolean"},
233231
},
234232
},
235233
{
@@ -331,7 +329,7 @@ var customSettingDefaults = map[string]map[string]any{
331329
"disallowedFolderNames": []string{},
332330
},
333331
"proxy/network": {
334-
"httpProxy": "", "httpsProxy": "", "noProxy": "", "username": "", "password": "",
332+
"enabled": false, "httpConnectorId": "", "httpsConnectorId": "",
335333
},
336334
"docker/mirror": {
337335
"mirrors": []any{}, "allowInsecureRegistries": false,
@@ -454,4 +452,4 @@ func cloneMap(input map[string]any) map[string]any {
454452
return map[string]any{}
455453
}
456454
return out
457-
}
455+
}

backend/domain/monitor/local_services.go

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"sync"
78
"strings"
89
"time"
910

@@ -24,10 +25,39 @@ type LocalServiceObservation struct {
2425
LogAvailable bool
2526
}
2627

27-
func ObserveLocalServices(registry *swcatalog.LocalRegistry) []LocalServiceObservation {
28+
var localServiceProcessInfoFn = func() ([]supervisor.ProcessInfo, error) {
2829
client := supervisor.NewClient(supervisor.DefaultConfig())
29-
processes, procErr := client.GetAllProcessInfo()
30+
return client.GetAllProcessInfo()
31+
}
32+
33+
var localServiceResourceFn = supervisor.GetProcessResources
34+
var localServiceMemoryFn = supervisor.GetProcessMemory
35+
var localServiceUptimeFn = supervisor.GetProcessUptime
36+
37+
var localServiceObservationCache = struct {
38+
mu sync.Mutex
39+
items []LocalServiceObservation
40+
initialized bool
41+
refreshing bool
42+
}{}
43+
44+
func ObserveLocalServices(registry *swcatalog.LocalRegistry) []LocalServiceObservation {
45+
if items, ok := loadLocalServiceObservationSnapshot(); ok {
46+
startLocalServiceObservationRefresh(registry)
47+
return items
48+
}
49+
50+
items := observeLocalServicesSnapshot(registry, false)
51+
storeLocalServiceObservationSnapshot(items)
52+
startLocalServiceObservationRefresh(registry)
53+
return items
54+
}
55+
56+
func observeLocalServicesSnapshot(registry *swcatalog.LocalRegistry, includeCPU bool) []LocalServiceObservation {
57+
processes, procErr := localServiceProcessInfoFn()
3058
resources := map[int]supervisor.ResourceInfo{}
59+
memoryByPID := map[int]int64{}
60+
uptimeByPID := map[int]int64{}
3161
processMap := map[string]supervisor.ProcessInfo{}
3262
if procErr == nil {
3363
pids := make([]int, 0, len(processes))
@@ -37,7 +67,12 @@ func ObserveLocalServices(registry *swcatalog.LocalRegistry) []LocalServiceObser
3767
pids = append(pids, process.PID)
3868
}
3969
}
40-
resources = supervisor.GetProcessResources(pids)
70+
if includeCPU {
71+
resources = localServiceResourceFn(pids)
72+
} else {
73+
memoryByPID = localServiceMemoryFn(pids)
74+
}
75+
uptimeByPID = localServiceUptimeFn(pids)
4176
}
4277

4378
now := time.Now().UTC().Format(time.RFC3339)
@@ -52,10 +87,14 @@ func ObserveLocalServices(registry *swcatalog.LocalRegistry) []LocalServiceObser
5287
if ok {
5388
state = strings.ToLower(process.StateName)
5489
pid = process.PID
55-
uptime = process.Uptime
90+
if observedUptime, exists := uptimeByPID[process.PID]; exists {
91+
uptime = observedUptime
92+
}
5693
if resource, exists := resources[process.PID]; exists {
5794
cpu = resource.CPU
5895
memory = resource.Memory
96+
} else if fastMemory, exists := memoryByPID[process.PID]; exists {
97+
memory = fastMemory
5998
}
6099
} else if procErr == nil {
61100
state = "missing"
@@ -76,6 +115,50 @@ func ObserveLocalServices(registry *swcatalog.LocalRegistry) []LocalServiceObser
76115
return items
77116
}
78117

118+
func loadLocalServiceObservationSnapshot() ([]LocalServiceObservation, bool) {
119+
localServiceObservationCache.mu.Lock()
120+
defer localServiceObservationCache.mu.Unlock()
121+
if !localServiceObservationCache.initialized {
122+
return nil, false
123+
}
124+
return cloneLocalServiceObservations(localServiceObservationCache.items), true
125+
}
126+
127+
func storeLocalServiceObservationSnapshot(items []LocalServiceObservation) {
128+
localServiceObservationCache.mu.Lock()
129+
localServiceObservationCache.items = cloneLocalServiceObservations(items)
130+
localServiceObservationCache.initialized = true
131+
localServiceObservationCache.mu.Unlock()
132+
}
133+
134+
func startLocalServiceObservationRefresh(registry *swcatalog.LocalRegistry) {
135+
localServiceObservationCache.mu.Lock()
136+
if localServiceObservationCache.refreshing {
137+
localServiceObservationCache.mu.Unlock()
138+
return
139+
}
140+
localServiceObservationCache.refreshing = true
141+
localServiceObservationCache.mu.Unlock()
142+
143+
go func() {
144+
items := observeLocalServicesSnapshot(registry, true)
145+
localServiceObservationCache.mu.Lock()
146+
localServiceObservationCache.items = cloneLocalServiceObservations(items)
147+
localServiceObservationCache.initialized = true
148+
localServiceObservationCache.refreshing = false
149+
localServiceObservationCache.mu.Unlock()
150+
}()
151+
}
152+
153+
func cloneLocalServiceObservations(items []LocalServiceObservation) []LocalServiceObservation {
154+
if len(items) == 0 {
155+
return []LocalServiceObservation{}
156+
}
157+
cloned := make([]LocalServiceObservation, len(items))
158+
copy(cloned, items)
159+
return cloned
160+
}
161+
79162
func LoadLocalServiceLog(service swcatalog.LocalService, stream string, maxBytes int) (string, bool, error) {
80163
switch service.LogAccess.Type {
81164
case "supervisor":
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package monitor
2+
3+
import (
4+
"sync"
5+
"testing"
6+
"time"
7+
8+
swcatalog "github.com/websoft9/appos/backend/domain/software/catalog"
9+
"github.com/websoft9/appos/backend/infra/supervisor"
10+
)
11+
12+
func TestObserveLocalServicesReturnsFastSnapshotThenBackgroundCPU(t *testing.T) {
13+
originalProcessInfoFn := localServiceProcessInfoFn
14+
originalResourceFn := localServiceResourceFn
15+
originalMemoryFn := localServiceMemoryFn
16+
originalUptimeFn := localServiceUptimeFn
17+
originalCache := localServiceObservationCache
18+
19+
localServiceObservationCache = struct {
20+
mu sync.Mutex
21+
items []LocalServiceObservation
22+
initialized bool
23+
refreshing bool
24+
}{}
25+
26+
t.Cleanup(func() {
27+
localServiceProcessInfoFn = originalProcessInfoFn
28+
localServiceResourceFn = originalResourceFn
29+
localServiceMemoryFn = originalMemoryFn
30+
localServiceUptimeFn = originalUptimeFn
31+
localServiceObservationCache = originalCache
32+
})
33+
34+
registry := &swcatalog.LocalRegistry{
35+
Version: 1,
36+
Components: []swcatalog.LocalComponent{
37+
{ID: "appos", Name: "AppOS", Enabled: true},
38+
},
39+
Services: []swcatalog.LocalService{
40+
{Name: "appos", ComponentID: "appos", Enabled: true, Lifecycle: "always_on", Visibility: "default"},
41+
},
42+
}
43+
44+
refreshed := make(chan struct{}, 1)
45+
localServiceProcessInfoFn = func() ([]supervisor.ProcessInfo, error) {
46+
return []supervisor.ProcessInfo{{Name: "appos", PID: 123, Uptime: 60, StateName: "RUNNING"}}, nil
47+
}
48+
localServiceMemoryFn = func([]int) map[int]int64 {
49+
return map[int]int64{123: 1024}
50+
}
51+
localServiceUptimeFn = func([]int) map[int]int64 {
52+
return map[int]int64{123: 60}
53+
}
54+
localServiceResourceFn = func([]int) map[int]supervisor.ResourceInfo {
55+
select {
56+
case refreshed <- struct{}{}:
57+
default:
58+
}
59+
return map[int]supervisor.ResourceInfo{123: {PID: 123, CPU: 1.5, Memory: 2048}}
60+
}
61+
62+
first := ObserveLocalServices(registry)
63+
if len(first) != 1 {
64+
t.Fatalf("expected 1 service, got %d", len(first))
65+
}
66+
if first[0].CPU != 0 {
67+
t.Fatalf("expected fast snapshot CPU 0 before background refresh, got %v", first[0].CPU)
68+
}
69+
if first[0].Memory != 1024 {
70+
t.Fatalf("expected fast snapshot memory 1024, got %d", first[0].Memory)
71+
}
72+
if first[0].Uptime != 60 {
73+
t.Fatalf("expected fast snapshot uptime 60, got %d", first[0].Uptime)
74+
}
75+
76+
select {
77+
case <-refreshed:
78+
case <-time.After(2 * time.Second):
79+
t.Fatal("expected background CPU refresh to complete")
80+
}
81+
82+
second := ObserveLocalServices(registry)
83+
if len(second) != 1 {
84+
t.Fatalf("expected 1 service after refresh, got %d", len(second))
85+
}
86+
if second[0].CPU != 1.5 {
87+
t.Fatalf("expected cached CPU 1.5 after background refresh, got %v", second[0].CPU)
88+
}
89+
if second[0].Memory != 2048 {
90+
t.Fatalf("expected refreshed memory 2048 after background refresh, got %d", second[0].Memory)
91+
}
92+
if second[0].Uptime != 60 {
93+
t.Fatalf("expected cached uptime 60 after background refresh, got %d", second[0].Uptime)
94+
}
95+
}

backend/domain/resource/connectors/compat_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ func TestResolveLLMTemplate(t *testing.T) {
3737
}
3838

3939
func TestDeclaredConnectorKindsHaveTemplates(t *testing.T) {
40-
declaredKinds := []string{KindRESTAPI, KindWebhook, KindMCP, KindSMTP, KindDNS, KindRegistry}
40+
declaredKinds := []string{KindRESTAPI, KindWebhook, KindMCP, KindSMTP, KindDNS, KindRegistry, KindProxy}
4141
for _, kind := range declaredKinds {
4242
t.Run(kind, func(t *testing.T) {
4343
templates := TemplatesByKind(kind)
@@ -56,6 +56,8 @@ func TestFindTemplateLoadsGenericNonLLMTemplates(t *testing.T) {
5656
{id: "generic-smtp", kind: KindSMTP},
5757
{id: "generic-dns", kind: KindDNS},
5858
{id: "generic-registry", kind: KindRegistry},
59+
{id: "http-proxy", kind: KindProxy},
60+
{id: "socks5-proxy", kind: KindProxy},
5961
}
6062

6163
for _, tc := range testCases {

backend/domain/resource/connectors/model.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const (
1313
KindSMTP = "smtp"
1414
KindDNS = "dns"
1515
KindRegistry = "registry"
16+
KindProxy = "proxy"
1617
)
1718

1819
const (
@@ -27,6 +28,7 @@ var declaredKinds = []string{
2728
KindSMTP,
2829
KindDNS,
2930
KindRegistry,
31+
KindProxy,
3032
}
3133

3234
func AllowedKinds() []string {

0 commit comments

Comments
 (0)