Cardinal Panel & Cardinal Wings Architecture: From In-Browser Terminal to cgroups v2
An in-depth technical analysis of multi-tier architecture: decoupling the Nuxt 4 + Drizzle control plane from the Go node daemon, WebSocket proxying, SSE log streams, and deterministic srv-<uuid> addressing.
- ▸The panel never accesses the host runtime directly: all node interactions are scoped through Wings authenticated REST boundaries.
- ▸Deterministic `srv-<uuid>` container naming eliminates database desynchronization during server rebuilds.
- ▸ANSI log follow streams use Server-Sent Events (SSE), while interactive PTY sessions run across authenticated WebSocket proxies.
- ▸The embedded SFTP daemon in Wings enforces strict per-container chroot boundaries into target volume directories.
1. Multi-Tier Topology & Decoupling
When designing a server hosting control plane, directly exposing container runtime sockets to the web layer introduces massive attack surfaces. Cardinal adopts a strict three-tier decoupling: browsers interact exclusively with the Nuxt 4 panel, the panel orchestrates remote nodes via Cardinal Wings, and Wings invokes local Cardinal runtime primitives.
[ Browser (Client) ]
│ HTTPS (:443) / WSS (WebSocket Terminal)
▼
[ Cardinal Panel (Nuxt 4 + Drizzle ORM) ] :3000
│ Bearer Auth · Multi-Node Routing
▼
[ Cardinal Wings Daemon (Go) ] :8080 (REST + SSE) · :2022 (SFTP)
│ Loopback IPC / HTTP
▼
[ Cardinal Runtime (serve) ] ~5 MB Go Static Binary
│ clone(2) · setns(2) · pivot_root · cgroups v2
▼
[ Isolated Linux Containers (Namespaces + OverlayFS) ]2. Deterministic Addressing: The `srv-<uuid>` Pattern
Traditional orchestration panels persist runtime-generated container IDs in the primary database. Upon server reinstallation or image re-pulling, these IDs mutate, causing state desynchronization. In the Panel + Wings architecture, containers use a deterministic naming scheme: `srv-<server-uuid>`. Wings incorporates an internal lookup resolver (`resolve.go`) that dynamically matches server UUIDs to live Cardinal runtime descriptors, surviving rebuilds with zero database migrations.
// ResolveContainer converts panel server reference to Cardinal container ID
func (r *Resolver) Resolve(serverRef string) (*ContainerMeta, error) {
if !strings.HasPrefix(serverRef, "srv-") {
serverRef = "srv-" + serverRef
}
container, err := r.cardinalClient.Inspect(serverRef)
if err != nil {
return nil, fmt.Errorf("server %s not found on this node: %w", serverRef, err)
}
return container, nil
}3. Streaming Logs & Interactive PTY Terminal
Console streaming is partitioned into two distinct primitives: continuous log following and interactive PTY execution. For stdout/stderr tracking, Wings exposes an SSE endpoint, streaming new lines with minimal network overhead compared to polling. For interactive terminals, the panel bridges browser WebSockets directly to Wings PTY execution sockets, feeding raw ANSI sequences into xterm.js.
4. Embedded Chrooted SFTP Server on Port :2022
Managing server configurations (such as server.properties or config.toml) demands secure filesystem operations. Rather than spawning host-level OpenSSH processes, Wings embeds a native Go SFTP daemon listening on port :2022. During handshake, Wings verifies server credentials and strictly chroots the SFTP session to the server volume directory, preventing directory traversal.
# Подключение через стандартный SFTP-клиент
sftp -P 2022 admin.srv-7f8a9b2c@node1.example.com
# Внутренняя структура директории контейнера в Cardinal:
/var/lib/cardinal/containers/srv-7f8a9b2c/
├── config.json # OCI спецификация процесса и cgroups v2
├── rootfs/ # Смонтированный OverlayFS слой
└── data/ # Персистентный том сервера (SFTP chroot root)5. Security Model: Scoped Bearer Tokens and Loopback
Wings defaults to loopback or mTLS-protected ingress. API endpoints require scoped Bearer authorization tokens (Read-Only, Operator, Node Admin), ensuring that compromised containers cannot access host-level control planes.
