Skip to main content
Features

Interactive Labs

Lab system architecture — LabLauncher provisioning, container/VM sessions, terminal and VNC access, challenge tracking, and dual-node failover

February 23, 2026

Interactive Labs

The lab system lets users spin up ephemeral Linux environments for hands-on learning. It supports real LXC containers, QEMU virtual machines, and a client-side simulation fallback. Sessions last 60 minutes with optional extensions.

Architecture

LabLauncher (health check + provisioning)

     ├── POST /api/labs/create → Proxmox provisions container/VM

     ├── Poll /api/labs/{id} until status: "running"

     └── Dispatch "lab-ready" event

              ├── SessionBar (countdown + resource monitoring)
              ├── TerminalEmbed (xterm.js via WebSocket)
              ├── VNCEmbed (noVNC via WebSocket, QEMU only)
              ├── ChallengeTracker (task progression)
              ├── DocumentationPanel (learning objectives)
              └── LabBubble (floating session indicator)

LabLauncher

File: src/components/labs/LabLauncher.astro

Entry point for all lab provisioning. Manages the full lifecycle:

  1. Session check: Looks in localStorage for an existing session for this template. If found, validates with the API and reconnects without re-provisioning.
  2. Health check: GET /api/labs/health with 2 retries and 12-second timeout. If the backend is unavailable, offers simulation mode.
  3. Provisioning: POST /api/labs/create with template_id. Receives session_id, session_token, expires_at, container_ips, and container_password.
  4. Polling: Hits GET /api/labs/{sessionId} every second until status: "running". Timeout: 30 seconds for LXC containers, 90 seconds for QEMU VMs.
  5. Ready: Dispatches lab-ready custom event with session details. All other components activate on this event.

Simulation mode: When the backend is unreachable, dispatches lab-simulation-mode and the page falls back to a client-side sandbox environment.

SessionBar

File: src/components/labs/SessionBar.astro

Fixed-position bar at the bottom of the screen showing:

ElementBehavior
Session IDShort identifier
CountdownMM:SS format, updates every second
CPU/MEM barsPolled every 10 seconds from the API
+15m buttonPOST /api/labs/{id}/extend — extends session
End Lab buttonDELETE /api/labs/{id} with confirmation

Timer color states: green (>10 min), yellow (5–10 min), red with blink (<5 min). Auto-destroys the session at 0:00. Registers a beforeunload handler to send a keepalive DELETE if the user closes the browser.

TerminalEmbed

File: src/components/labs/TerminalEmbed.astro

Browser-based terminal using xterm.js with FitAddon and WebLinksAddon.

Connection: WebSocket to wss://labs.Arcturus-Prime.com/ws/terminal/{sessionId}?token={token}&container_id={n}. Multi-container labs get separate embeds with different container_id values (0-indexed).

Data flow:

  • User types → terminal.onData() → WebSocket send
  • Backend sends → onmessageterminal.write()
  • Terminal resize → onResize() → JSON resize payload to WebSocket

Reconnection: Up to 5 attempts with exponential backoff (2s, 4s, 6s, 8s, 10s). A connection is considered stable after 10 seconds without disconnect, which resets the attempt counter.

Theme: Dark background (#0a0e17) with a cyan cursor (#22d3ee) and the Arcturus-Prime color palette for ANSI colors.

VNCEmbed

File: src/components/labs/VNCEmbed.astro

Graphical console for QEMU VMs using noVNC (self-hosted ES modules in /public/novnc/).

Connection flow:

  1. Browser creates WebSocket to wss://labs.Arcturus-Prime.com/ws/vnc/{sessionId}?token={token}
  2. Server sends VNC ticket as the first text message (Proxmox session token)
  3. noVNC RFB instance is created with the ticket as credentials
  4. Display scales to container with quality level 6, compression level 2

Fullscreen: F11 key or button. Chrome bar auto-hides on hover. ResizeObserver re-triggers scaling.

Reconnection: Up to 8 attempts. Server error close codes (4xxx+) display human-readable reasons.

ChallengeTracker

File: src/components/labs/ChallengeTracker.astro

Interactive sidebar with lesson-based task progression.

Challenge structure:

interface Challenge {
  id: string;
  name: string;
  description: string;
  difficulty?: 'beginner' | 'intermediate' | 'advanced' | 'expert';
  tasks: Array<string | { label: string; steps?: string[]; hint?: string; command?: string; expected?: string }>;
}

Features:

  • Difficulty badges (B/I/A/E) with color coding and filter buttons
  • Expandable task lists with per-challenge progress (3/5)
  • Click tasks to toggle completion
  • Expand task details to see step-by-step instructions, copyable commands, hints, and expected output
  • Overall progress bar across all challenges
  • Progress saved to localStorage: Arcturus-Prime-challenges-{templateId}

DocumentationPanel

File: src/components/labs/DocumentationPanel.astro

Collapsible reference panel with three sections:

  • What You Can Do — Bulleted capabilities list
  • Learning Objectives — Goals grouped by difficulty tier
  • Resources — External documentation links

Toggle via the book icon header. Slides open with smooth animation.

LabBubble

File: src/components/labs/LabBubble.astro

Floating indicator (bottom-left, z-index 9998) showing all active lab sessions site-wide.

  • Collapsed: Green pulsing dot + “X Labs” count
  • Expanded: List of sessions with name, countdown, Open/Stop buttons
  • Validates sessions every 30 seconds, removes expired ones
  • Hidden when SessionBar is visible (SessionBar takes precedence)

Lab API Client

File: src/lib/lab-api.ts

All API requests are HMAC-SHA256 signed:

Signature = HMAC(timestamp:method:path, LAB_API_SECRET)
Headers: X-Lab-Token, X-Lab-Timestamp, X-Session-Token

Key functions: labFetch() (signed requests), labWebSocketUrl() (terminal WS URL), labVNCWebSocketUrl() (VNC WS URL), destroyLab() (DELETE with keepalive), and session storage helpers (storeSession, getStoredSessions, removeStoredSession).

Base URL: https://labs.Arcturus-Prime.com in production (Cloudflare Tunnel), relative URLs in dev (Vite proxy).

Event System

EventDispatched ByListenersDetail
lab-readyLabLauncherSessionBar, TerminalEmbed, VNCEmbed, LabBubblesessionId, token, expiresAt, templateId, containerIps, containerPassword, containerCount
lab-endedSessionBar, TerminalEmbed, LabBubbleLabLauncher, SessionBar, LabBubblesessionId
lab-simulation-modeLabLauncherPage scripts(no detail)

Session Lifecycle

DurationDefaultExtendable
Session TTL60 minutes+15 min per click (unlimited)
Auto-destroyOn expiration or user “End Lab”
Browser closebeforeunload sends DELETE with keepalive: true

Dual-Node Infrastructure

Labs provision across two Proxmox hypervisors connected via Tailscale subnet routing:

NodeRoleTypes
Primary (Izar)Main hypervisorLXC containers
Secondary (Tarn)Remote hypervisorQEMU VMs

The backend orchestrator handles placement decisions. Multi-container labs can receive container_ips arrays spanning both nodes. The 90-second QEMU timeout accounts for cross-site placement.

labsplaygroundcontainerslxcqemuvncterminalxterm