No description
  • TypeScript 81.6%
  • Svelte 18.4%
Find a file
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Julian Imhof 45df74e3bb
feat: schema shows only the terminal when console is enabled
Status fields card stays for the disabled state, where it explains
how to enable the console.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 01:01:41 +02:00
backend feat: schema shows only the terminal when console is enabled 2026-07-18 01:01:41 +02:00
frontend feat: host PTY terminal (drop docker), >_ icon 2026-07-17 16:59:56 +02:00
CLAUDE.md Initial commit 2026-07-17 16:11:35 +02:00
opsdeck.module.json docs: transient-prompt over-erase fixed via UTF-8 pty locale (v0.4.2) 2026-07-17 20:46:01 +02:00
README.md docs: transient-prompt over-erase fixed via UTF-8 pty locale (v0.4.2) 2026-07-17 20:46:01 +02:00

OpsDeck console module

An interactive terminal for OpsDeck: a real shell on the OpsDeck host, in the browser.

Security — read this first

This runs a real pseudo-terminal shell as the OpsDeck server process. That process typically has host mounts and the Docker socket, so this is effectively a root shell on the host. Accordingly it is:

  • Off by default. The operator must set OPSDECK_MOD_CONSOLE_ENABLE=1 on the server to turn it on.
  • Admin-only, enforced server-side on the WebSocket route.

Enable it only where you would hand out SSH root access.

What it is

A genuine PTY (not the old per-command runner): colors, line editing, job control, and curses apps (top, vim, htop) all work. The server allocates the pty and bridges it to an xterm.js terminal in the browser over a WebSocket.

xterm.js (browser) ──ws /api/mod/console/pty──▶ ctx.upgradeWebSocket
     ▲                                                │
     └──────────── pty master bytes ◀── ctx.terminal.open() ──▶ shell

Two modes

  • Container shell (default): a shell inside the OpsDeck container. That process has the host mounts + docker socket, so it's already powerful, but the environment is the container's (bash, no host dotfiles).

  • Host login shell (OPSDECK_MOD_CONSOLE_CHROOT_HOST=1): chroot into the mounted host root and drop to a host user — their own login shell, init files (~/.bashrc/.zshrc), $PATH, home, and supplementary groups (sudo, docker, …). As close to ssh user@host as you get from a container. Needs CAP_SYS_CHROOT (a docker default — already present) and the container running as root (it does). Works under no-new-privileges (the uid drop is a direct setuid from root, no setuid binary).

    Read-only: the /host/root bind is read_only: true in the compose, so the host shell can read and run anything but not write. For a read-write host shell, flip that mount to read_only: false — that lets the container modify the host, so decide deliberately.

Config

Env Default Meaning
OPSDECK_MOD_CONSOLE_ENABLE unset 1 enables the terminal; anything else keeps it off
OPSDECK_MOD_CONSOLE_CHROOT_HOST unset 1 = host login shell (chroot into the host root as a user); else container shell
OPSDECK_MOD_CONSOLE_USER 1000 host user (name or uid) for chroot mode
OPSDECK_MOD_CONSOLE_SHELL /bin/bash shell for container mode (chroot mode uses the user's passwd shell)
OPSDECK_MOD_CONSOLE_CWD host root mount start dir for container mode (chroot mode uses the user's home)

Requirements

OpsDeck core new enough to provide ctx.terminal (with chroot/user) + ctx.upgradeWebSocket. The pty uses libc + setsid/chroot; the container runs as root with CAP_SYS_CHROOT (docker default). The terminal is web-only; non-web clients see a status page via the schema.

Limitations

  • One shell per browser session (open the page again for another).

The terminal resizes live with the browser window, and shell-integration escape sequences (prompt marks, title, cwd) render normally. Transient prompts (oh-my-posh transient_prompt, powerlevel10k) work correctly — provided core launches the shell with a UTF-8 ctype locale (see below).

Note: UTF-8 locale is required

A shell drawing Nerd-Font / box-drawing glyphs must run under a UTF-8 LC_CTYPE. Without one, glibc falls back to C/POSIX and wcwidth() counts each byte of a multibyte glyph ( and powerline separators are 3 bytes) as a column, so the shell mis-measures its own prompt width — which makes transient-prompt redraws over-erase real scrollback (ESC[<n>A overshoots). Core's pty sets LANG=LC_CTYPE=C.UTF-8 by default, so this is handled; if you override the pty env, keep a UTF-8 ctype locale.

Install

Production — pinned by commit:

OPSDECK_EXTERNAL_MODULES=https://git.imhof.cloud/OpsDeck/module_console.git#<sha>
# then, to actually enable the shell:
OPSDECK_MOD_CONSOLE_ENABLE=1

Dev loop — see CLAUDE.md.

Reference

Built from the OpsDeck module template. ctx.terminal / ctx.upgradeWebSocket and the @opsdeck/ui Terminal component are documented in core/docs/modules.md.