Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
## 功能

- **Supervisor 面板** —— 通过 supervisord 托管的进程:启停、保活、查看日志、端口归属
- **应用市场 / 应用管理** —— 基于本机 Kubernetes 的容器化应用一键安装与管理(可选)
- **应用市场 / 应用管理** —— Docker Compose 与本机 Kubernetes 双运行时;支持内联 Compose、平台商店和第三方 HTTP/Git catalog(可选)
- **本地模型** —— 模型目录扫描、容量、可用 runtime 视图
- **文件浏览器** —— 工作区文件浏览
- **Web 终端** —— 浏览器内交互式 shell
Expand Down Expand Up @@ -59,7 +59,7 @@ pkg/
config/ 配置加载与校验
console/ 本地 HTTP / WebSocket 服务(控制台后端)
supervisor/ supervisord 客户端封装
apps/ K8s 应用管理 + 应用市场客户端
apps/ Compose/K8s 应用领域、异步任务与应用市场
collector/ 系统 / GPU / 设备指标采集
alerts/ 本地告警规则引擎
files/ 文件浏览
Expand Down
96 changes: 77 additions & 19 deletions cmd/devbox/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (
"fmt"
"os"
"os/signal"
"path/filepath"
"syscall"
"time"

"github.com/a2d2-dev/devbox/pkg/apps"
"github.com/a2d2-dev/devbox/pkg/collector"
Expand Down Expand Up @@ -49,16 +51,28 @@ func main() {
col := collector.New(logger, version)
go col.Start(ctx)

// K8s 应用管理器:可选,初始化失败仅禁用相关功能。
var appMgr *apps.Manager
if mgr, err := apps.NewManager(logger, apps.Config{
Kubeconfig: cfg.Kubernetes.Kubeconfig,
Namespace: cfg.Kubernetes.Namespace,
}); err != nil {
logger.Warn("K8s app manager unavailable; app management disabled", zap.Error(err))
// 应用管理 Controller(统一 K8s + Docker Compose 运行时,Issue #2)。
// 装配失败仅禁用应用管理,不影响控制台其它功能。
var appController apps.Controller
var appCleanup func()
if c, cleanup, err := apps.AssembleController(ctx, apps.ControllerConfig{
DataDir: cfg.Compose.DataDir,
DockerSocket: cfg.Compose.DockerSocket,
ComposeEnabled: cfg.Compose.Enabled,
Kubeconfig: cfg.Kubernetes.Kubeconfig,
Namespace: cfg.Kubernetes.Namespace,
KubernetesEnabled: true,
}, logger); err != nil {
logger.Warn("App controller unavailable; app management disabled", zap.Error(err))
} else {
appMgr = mgr
appController = c
appCleanup = cleanup
}
defer func() {
if appCleanup != nil {
appCleanup()
}
}()

// 应用商店管理器:仅在显式配置 APIServerURL 时启用。
var storeMgr *apps.StoreManager
Expand All @@ -74,18 +88,41 @@ func main() {
}
}

// Catalog source 聚合:启动 YAML 来源 + apps.db 动态 1Panel 来源。
// 两者共用同一 SQLite 事实源;YAML 来源只读且优先。
configuredSources := toCatalogSources(cfg.Compose.Catalogs)
cacheRoot := filepath.Join(cfg.Compose.DataDir, "catalog-cache")
catalogs := apps.NewCatalogSetFromConfigs(configuredSources, cacheRoot, logger)
var catalogSourceManager *apps.CatalogSourceManager
if err := os.MkdirAll(cfg.Compose.DataDir, 0o750); err != nil {
logger.Warn("Catalog source data directory unavailable", zap.Error(err))
} else if sourceRepo, err := apps.OpenRepository(ctx, apps.CatalogDBPath(cfg.Compose.DataDir)); err != nil {
logger.Warn("Dynamic catalog source storage unavailable", zap.Error(err))
} else {
defer sourceRepo.Close()
catalogSourceManager = apps.NewCatalogSourceManager(sourceRepo, configuredSources, catalogs, cacheRoot, logger)
if err := catalogSourceManager.Reload(ctx); err != nil {
logger.Warn("Dynamic catalog sources load failed", zap.Error(err))
}
}
poll := time.Duration(cfg.Compose.CatalogPoll) * time.Second
go catalogs.Start(ctx, poll)
logger.Info("Catalog sources started", zap.Int("configured_sources", len(cfg.Compose.Catalogs)), zap.Duration("poll_interval", poll))

consoleServer := console.NewServer(logger, console.Config{
Enabled: cfg.Console.Enabled,
Port: cfg.Console.Port,
StaticDir: cfg.Console.StaticDir,
WorkDir: cfg.Console.WorkDir,
SupervisorSocket: cfg.Console.SupervisorSocket,
SupervisorConfDir: cfg.Console.SupervisorConfDir,
ConsoleURL: cfg.Console.ConsoleURL,
AuthPassword: cfg.Auth.Password,
AuthSessionTTL: cfg.Auth.SessionTTL,
LinksPath: cfg.Console.LinksPath,
}, col, appMgr, storeMgr)
Enabled: cfg.Console.Enabled,
Port: cfg.Console.Port,
StaticDir: cfg.Console.StaticDir,
WorkDir: cfg.Console.WorkDir,
SupervisorSocket: cfg.Console.SupervisorSocket,
SupervisorConfDir: cfg.Console.SupervisorConfDir,
ConsoleURL: cfg.Console.ConsoleURL,
AuthPassword: cfg.Auth.Password,
AuthSessionTTL: cfg.Auth.SessionTTL,
LinksPath: cfg.Console.LinksPath,
Catalogs: catalogs,
CatalogSourceManager: catalogSourceManager,
}, col, appController, storeMgr)

go func() {
if err := consoleServer.Start(ctx); err != nil {
Expand Down Expand Up @@ -129,3 +166,24 @@ func initLogger(cfg config.LoggingConfig) (*zap.Logger, error) {

return zapCfg.Build()
}

// toCatalogSources 把 config.CatalogSourceConfig 映射为 apps.CatalogSource。
// 字段一一对应;token 作为 secret 透传(不入日志/审计;git 经 http.extraHeader 注入)。
func toCatalogSources(cfgs []config.CatalogSourceConfig) []apps.CatalogSource {
out := make([]apps.CatalogSource, 0, len(cfgs))
for _, c := range cfgs {
out = append(out, apps.CatalogSource{
ID: c.ID,
Name: c.Name,
Kind: c.Kind,
URL: c.URL,
Platform: c.Platform,
Host: c.Host,
Ref: c.Ref,
Path: c.Path,
Token: c.Token,
Insecure: c.Insecure,
})
}
return out
}
30 changes: 30 additions & 0 deletions config.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,36 @@ kubernetes:
# 应用市场 API 地址;留空则禁用应用市场
apiserver_url: ""

# Docker Compose 应用管理;Docker 不可用时仅该运行时降级。
compose:
enabled: true
data_dir: "/var/lib/devbox"
# 仅允许本机绝对 Unix socket;不支持 tcp/http/远程 Docker endpoint。
# devbox 会固定 CLI 到这个 socket,不继承 DOCKER_HOST/DOCKER_CONTEXT。
docker_socket: "/var/run/docker.sock"
# 第三方 catalog 刷新间隔(秒);0 表示仅启动/手动刷新。
catalog_poll: 300
catalogs:
# HTTP 文件源:读取 <url>/catalog.json 及其相对 compose 文件。生产应使用 HTTPS。
# - id: "team-http"
# name: "团队应用"
# kind: "http"
# url: "https://apps.example.com/devbox"
# Git 仓库源:HTTPS shallow clone;不接受 SSH、file:// 或本地路径。
# - id: "community"
# name: "社区 Compose"
# kind: "git"
# url: "https://github.com/example/devbox-catalog.git"
# ref: "v1"
# path: "catalog"
# 原生 1Panel 开源应用商店;ref 留空使用远端默认分支(官方源当前为 dev)。
# - id: "onepanel-official"
# name: "1Panel 官方商店"
# kind: "1panel"
# url: "https://github.com/1Panel-dev/appstore"
# 私有源可配置只读 token;建议通过受控配置文件注入并限制文件权限。
# token: ""

logging:
# debug / info / warn / error
level: "info"
Expand Down
1 change: 1 addition & 0 deletions console-ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"test": "node --test src/lib/compose.test.js",
"preview": "vite preview"
},
"dependencies": {
Expand Down
5 changes: 4 additions & 1 deletion console-ui/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { Dock } from './components/Dock'
import AppWindow, { btnSecondary, btnPrimary } from './components/AppWindow'
import DashboardApp from './pages/Dashboard'
import AppStore from './pages/AppStore'
import { ComposeManager } from './pages/ComposeManager'
import AlertCenter from './pages/AlertCenter'
import AuditLog from './pages/AuditLog'
import Supervisor from './pages/Supervisor'
Expand Down Expand Up @@ -423,14 +424,16 @@ export default function App() {
>
{appId === 'dashboard' && <DashboardApp onOpenApp={launchApp}/>}
{appId === 'store' && <AppStore onOpenApp={launchApp} authed={authed} onRequireAuth={requireAuth}/>}
{appId === 'compose-manager' && <ComposeManager authed={authed} onRequireAuth={requireAuth}
onOpenStore={() => launchApp({ id: 'store' })} onOpenApp={launchApp}/>}
{appId === 'alerts' && <AlertCenter authed={authed} onRequireAuth={requireAuth}/>}
{appId === 'audit' && <AuditLog/>}
{appId === 'supervisor'&& <Supervisor/>}
{appId === 'virtual-machines' && <VirtualMachines/>}
{appId === 'hardware' && <Hardware/>}
{appId === 'links' && <Links/>}
{(appId === 'diag' || appId === 'settings') && <Diagnostics/>}
{!['dashboard','store','alerts','audit','supervisor','virtual-machines','hardware','links','diag','settings'].includes(appId)
{!['dashboard','store','compose-manager','alerts','audit','supervisor','virtual-machines','hardware','links','diag','settings'].includes(appId)
&& <AppShell appId={appId} app={app} authed={authed} onRequireAuth={requireAuth}
onOpenManagement={() => setMgmtOpen(true)}/>}

Expand Down
Loading