Resizable TTY interface

Draws a colored terminal frame and redraws it when the client window changes size.

Port 30025Auth: none

Connect

Connect with a TTY

ssh -t -p 30025 demo@modernssh-examples.manaf.ch

Test without a TTY

ssh -T -p 30025 demo@modernssh-examples.manaf.ch
server.ts
import { once } from "node:events"
import { Server, SessionChannel, type SessionWindowDimensions } from "@bunkerch/modernssh"

const hostKey = process.env.SSH_HOST_KEY_PRIVATE_KEY
if (!hostKey) throw new Error("SSH_HOST_KEY_PRIVATE_KEY is required")

const esc = "\u001b["

function frame({ columns, rows }: Readonly<SessionWindowDimensions>): string {
  if (columns < 36 || rows < 12) return esc + "2J" + esc + "HResize to at least 36 × 12."

  const width = Math.min(columns, 72)
  const inner = width - 2
  const line = (text = "", color = "") => {
    const clipped = text.slice(0, inner)
    const centered = clipped.padStart(Math.floor((inner + clipped.length) / 2)).padEnd(inner)
    return "│" + color + centered + "\u001b[0m│"
  }

  return esc + "H" + [
    "╭" + "─".repeat(inner) + "╮",
    line("modernssh TTY", "\u001b[1;35m"),
    line(),
    line("Resize the window and this frame will follow."),
    line(columns + " columns × " + rows + " rows"),
    line(),
    line("Press q or Ctrl-C to disconnect"),
    "╰" + "─".repeat(inner) + "╯",
  ].join("\r\n") + esc + "J"
}

const server = new Server({ hostKeys: [hostKey] })
server.hooker.hook("noneAuthentication", (_hook, _context, decision) => {
  decision.allowLogin = true
})
server.hooker.hook("channelOpenRequest", (_hook, channel, decision) => {
  decision.allowOpen = channel instanceof SessionChannel
})

server.on("connection", (connection) => {
  connection.on("channel", (channel) => {
    if (!(channel instanceof SessionChannel)) return
    let dimensions: Readonly<SessionWindowDimensions> | undefined
    let shell = channel.shell

    channel.hooker.hook("ptyRequest", (_hook, requested, decision) => {
      dimensions = requested
      decision.success = true
    })
    channel.hooker.hook("shellRequest", (_hook, decision) => {
      decision.success = true
    })
    channel.hooker.hook("windowChange", async (_hook, resized) => {
      dimensions = resized
      if (shell) await shell.writeStdout(frame(resized))
    })

    channel.events.on("shell", (openedShell) => {
      shell = openedShell
      if (!channel.pty || !dimensions) {
        void openedShell.writeStdout("No TTY detected. Reconnect with ssh -t.\n")
          .then(() => openedShell.exit(1).close())
        return
      }

      void openedShell.writeStdout(esc + "?1049h" + esc + "?25l" + frame(dimensions))
      openedShell.on("data", (data: Buffer) => {
        if (!data.includes(0x03) && !data.includes(0x71)) return
        void (async () => {
          await openedShell.writeStdout(esc + "?25h" + esc + "?1049l")
          const closed = once(openedShell, "close")
          openedShell.exit(0).close()
          await closed
          await connection.close()
        })()
      })
    })
  })
})

server.listen({ host: "0.0.0.0", port: Number(process.env.PORT ?? 2225) })